How to Capture Multiple Matches and Put Them Inside Variables

Hey Guys,

I'm trying to get the multiple matches and put them inside the variables.
In the following example, the macro search in the "Clipboard text" :

  • Title, first name, last name,
  • email,
  • itinerary "from"
  • itinerary "to"
  • Date

The issue I encounter is when I have multiple matches with one macro, like multiple passengers and multiple flight date (with a return flight instead of one-way flight).

What I would like to do :
I want all the variable to be shown in the "Display Text Popup" I have at the end of the macro I tried to use the "For each item" but can't make it work...

Info : the copied source data has always the exact same layout. The only difference is sometimes there will be only one passenger and only one way ticket (so no return date).

Thanks a lot for your help guys !


Title		First name		Last name
Mr		John Martin		Doe Santos
Ms		Estelle Lana		Johnson Santos


Lagos (LOS), NG  
London (LHR), GB
27 - Jan - 2022

London (LHR), GB  
Lagos (LOS), NG		Date
21 - Feb - 2022

Example Booking.kmmacros (5.7 KB)

Is the copied source data always in exactly the same layout as your example? i.e. is it always in this form, with line breaks and tabs the same?
Title First name Last name
Mr John Doe
Lima (LIM), PE
Medellín (EOH), CO

Hey Ryan,

One quick tip.

When pulling data out of a text record it is often a good idea to break the job into small tasks rather than try to create a monolithic regex.



@Zabobon Yes it is ! I've also uploaded the .kmmacros file in the first post where you can see how the format is when copied.

@ccstone this is what I tried to do. I have one macro for each data I try to pull. But the issue is when I have multiple data for that macro (date and passengers).

Hey Ryan,

I don't think you've explained clearly what your desired outcome is in that case...


@ccstone sorry about that.
As you can see here the macro for the date has 2 matches (because there is a return date for the flight). Keyboard Maestro only render the first match and not the second. I would like to know how can I render the second?
I have the same issue with the passenger informations.

You haven't explained what you mean by render...

Your macro copies data to variables. What do you want the outcome to be when you have multiple groups of data?

Be specific.

I want all the variable to be shown in the "Display Text Popup" I have at the end of the macro.

The problem you have given yourself is tricky because the only way to know whether a date is outward or return is to do with its placement in the string of data (they are both dates in exactly the same format).

Looking at your example data, it seems that from the word "Itinerary" onwards the positions of each bit of data will always be the same. So, outward date will be 8th on from "Itinerary" and return date will 16th on (if I've counted right).
But at least that is a bit of logic that you should be able to leverage.

As @ccstone says:

Okay, here is an approach that might get you started (there will be other methods but here's how I would go about it). The idea is to first clean up the data so that each item is simply separated by single commas (rather than multiple line breaks and tabs as it is now). This can be done using Keyboard Maestro's built in Search and Replace Action (or by Regex).

Basically just look for tabs and replace with line breaks. Then look for multiple line breaks and replace with single line breaks. Then replace all line breaks with commas.



Becomes this:


Now, if that it saved as a Variable, it is in a format that Keyboard Maestro can easily work with (each actual bit of data separated by a comma).

Say this cleaned up Variable is called: %Variable%Clipboard text% then the first piece of data in that Variable can be accessed and displayed with the text syntax %Variable%Clipboard text[1]% ( and the eighth bit of data can be accessed and displayed with %Variable%Clipboard text[8]% (John).

This relatively simple extraction will fall over when you get beyond the passenger names because the position number of items after that will vary depending on how many passengers there are.

However, you are still in a better position than before because at least now you can apply some logic to numbered bits in the Variable to find out what they are and extract them.

The outward date will be the 8th bit of data after the bit that contains the word "Itenerary". The return date will be the 16th bit of data after the bit that contains the word "Itenerary"

It might be there is a similar approach using Regex alone (others here maybe can suggest one). But the key to understanding why your original approach couldn't distinguish between outward and return dates is that it didn't take account of where those dates were positioned in the text string in relation to other bits of data in the text string.

Here is an example of how I would do this with native Keyboard Maestro actions. I'm sure there are better ways with Regex etc - but this gives an example of applying the logic. To make the process more robust you could also check that the two pieces of data are actual dates rather than something else.

EXAMPLE Find Outward and Return Date by Position after "Itinerary".kmmacros (7.1 KB)

Click to show image of Example Macro

EXAMPLE Find Outward and Return Date by Position after "Itinerary"


I agree with @Zabobon that the best way to handle this is to by splitting the input into sections and deal with each section individually. And because the Passenger and Itinerary sections may have multiple entries, they require their own divide-and-conquer approaches.

Because I still don't really understand splitting in Keyboard Maestro, I did it in Perl. Perhaps you or someone else can translate it into native KM commands.

Itinerary.kmmacros (2.8 KB)

The Perl code is here:

Perl script for parsing and printing itinerary

# Read in all the text.
	local $/ = undef;
	$allText = <>;

# Break the text into sections based on headers and blank lines.
# The "Passengers" and "Itinerary" headers will be the second
# and fourth sections.
@sections = split /\s+(Passengers|Itinerary)\s+/s, $allText;

# Email address is the first section.
$email = $sections[0];
$email =~ s/\s+$//s;

# Passenger list is the third section.
@passengers = (split /\R/, $sections[2]);
shift @passengers;
foreach $p (@passengers) {
	$fullname = join(" ", split(/\t+/, $p));
	push @pnames, $fullname;

# Itinerary is the fifth section. There may be multiple trips.
@trips = (split /\R\R+/, $sections[4]);
foreach $t (@trips) {
	@parts = split /(From|To|Date)\s+/s, $t;
	# The first part is empty, so we'll get rid of it.
	shift @parts;
	# The From, To, and Date parts are now second, fourth, and sixth.
	$from = $parts[1];
	$to = $parts[3];
	$date = $parts[5];
	# Extract just the substrings we want.
	$from =~ s/^.+\((.+)\).+$/$1/s;
	$to =~ s/^.+\((.+)\).+$/$1/s;
	$date =~ s/\s*-\s*/ /g;
	$date =~ s/\s*$//s;
	push @tinfo, "From: $from, To: $to\nThe $date";

# Make a new list for each trip/passenger combination.
foreach $t (@tinfo) {
	foreach $p (@pnames) {
		push @info, "$email\n$p\n$t";

# Print the list.
print join("\n\n", @info);

Your "Display Text" step seemed to account for only one passenger and trip, so I had my code output that same info for each passenger/trip combination.

After importing, the macro will be in a group called "Temporary" and it will be enabled.


I forgot to show what the output looks like:
Mr John Martin Doe Santos
From: LOS, To: LHR
The 27 Jan 2022
Ms Estelle Lana Johnson Santos
From: LOS, To: LHR
The 27 Jan 2022
Mr John Martin Doe Santos
From: LHR, To: LOS
The 21 Feb 2022
Ms Estelle Lana Johnson Santos
From: LHR, To: LOS
The 21 Feb 2022

Thank you really much guys, this was really helpful. I will work on this and give you an update of the final solution I came up with.

Amazing community ! :pray: