For Each + Search and Replace (finally making sense)

This is a loooooong post and something most of you who have been using Keyboard Maestro for a long time will find too obvious, but now that I finally understand this combo (For Each + Search and Replace), I decided to create a note for myself in case I forget about it again, and decided to share it here in case others struggle with the same issue.

As always, feel free to share your feedback or if something is not correct.


The For Each action is a loop that runs all the actions inside execute the following actions section as many times as there are lines in a variable / Finder selection / Finder location / clipboard, etc.
So if my variable (or any of the other options above) contains these 5 lines:

My name is...
My age is...
I live in...
My exercise routine is...
I like to eat...

The For Each action (loop) will run the actions inside the execute the following actions section, 5 times, because there are 5 lines.

If I add the main variable (or any of the other options above) as my target in the Search and Replace action like this
Pasted image 20240720095951

... it will analyze and make any necessary changes to the whole variable Local__Code, 5 times.

Example with the Local_Code variable being this:

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: "";
  position: absolute;
  display: none;
}

... and the For Each action being this:
Pasted image 20240720100148

The first time it runs, it will replace all a in Local__Code (not the Local__line variable -- this is what was making everything confusing to me) with @:

So I will get this result, even after just the first run:

/* Cre@te the checkm@rk/indic@tor (hidden when not checked) */
.checkm@rk:@fter {
  content: "";
  position: @bsolute;
  displ@y: none;
}

... because I was using the main variable Local__Code in my Search and Replace target, not Local_line.
Now even if it runs 5 more times, nothing will change, because all a were already replaced with @, but in other cases as I share lower in this post, it makes a difference.

Initially I was thinking that the workflow was:
"In each line of variable XYZ (in this case it was Local__Code), run these actions below, and automatically update the variable XYZ with the new changes on that line."
So it would read the first line, make the changes only to that line, and update the first line in XYZ variable with the modified line. Move on to the second line, repeat. At the end of the loop I would get the XYZ variable updated with all the changes.

That's why I was always under the impression that the workflow that would make sense to me was that I should use the variable inside the For Reach text field (in this case Local__line) as my Search and Replace target, but then if I do that, I have to do something with that variable in order to create a new list with all the modified lines (more on that at the bottom of the post).

Now when I'm trying to replace "something" with another "something" that also has the previous "something", and there's more than 1 line, then I have a problem if I'm using the main variable Local__Code as my target.
Example: replacing " with \" in the line content: "";

This first time I run this:
Pasted image 20240720100644

I will get this, as expected:

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: \"\";
  position: absolute;
  display: none;
}

but the second time I run it (because the variable has 6 lines, so it will run all actions 6 times), it will now replace both " in \"\" with \"
Pasted image 20240720102836

So " will be replaced with \" giving me \\" after the second run:

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: \\"\\";
  position: absolute;
  display: none;
}

Since I have 6 lines, it runs 6 times and I get this at the end of the loop:

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: \\\\\\"\\\\\\";
  position: absolute;
  display: none;
}

If I just want to replace it once and get this:

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: \"\";
  position: absolute;
  display: none;
}

... then I need to use the variable Local__line as my target so Keyboard Maestro can make changes to that line alone, not the whole Local__Code variable. Then I need an action to add that changed variable to a new variable (e.g. Local__finalCode) and on each run it will keep adding each modified line Local__line to the new Local__finalCode variable:
Pasted image 20240720103705

2 Likes

Great post, and a perfect example of how we make assumptions about why something's happening, and if those assumptions turn out to be wrong, we often find ourselves going down long rabbit trails that are the wrong trails, but we still learn things along the way.

And this is exactly what happens to me all the time. Well, not "all the time", but you know what I mean. It can be equal parts frustrating and educational.


Now for the forehead-slapping moment (unless I'm mistaken, which I don't think I am). You could have done this with one action - no need for the "For each":

image

But think how much knowledge you wouldn't have gained!

And by the way, this happens to me frequently, and usually at the hands of someone else asking me "Why didn't you just do it this way...?"

1 Like

Thanks Dan!

Yes, it's easy to make assumptions and seeing ourselves waste so much time and energy along the way.
But as long as we don't quit and eventually understand how things work, that's always a good lesson. It feels great now that I understand what's happening. It was really making me so frustrated!

Yes, for this particular example that would be the case.
I actually used this example, just because it was something I was working on when I had the light bulb moment so I went along with it. Especially because of the issue that I was replacing "something" with "something" that contained the first "something" :joy:
I was getting all these slashes and I couldn't understand why.

Exactly. Sometimes doing it the hard way is the best way to learn.

As we were discussing earlier, I get that a lot as well, especially when it comes to someone who's experienced when it comes to JXA and provides 2-3 lines that solve the issue I was trying to solve (and ended up solving) using 15 actions... and 3 hours of my time haha

But again, along the way I usually learn something new, so that's good :slight_smile:

1 Like

I just had one of those "How did I not know this?" things with KM. It never ends.

There's one other thing I didn't mention previously, although I thought about it:

If you find yourself having to use a "For/each" to go line-by-line through some text in order to do a search/replace, there's probably a much faster and easier way to do it with regular expressions.

So the next time something like that comes up, you might ask if anyone here knows a regular expression that could help. It's a good way to start learning regular expressions.

Just a thought. Glad you're having fun with KM!