After 2 hours of debugging, and searching this website, (and trying many things) I can't figure out how to pass pipe characters to the Execute Shell Script action, I want to do something like this (here's the simplest example that fails):
But if I write the variable to a text file, it works fine. It seems that when you try to execute the same code as a script, it fails to interpret the pipe command (|) as a pipe, but rather as a filename.
I don't want to use script files as a workaround, because that adds complications like adding execute permissions, and handling different paths for running different computers, etc. I just want Execute Shell Script to handle pipes, like files can handle pipes.
I'm able to get it to work for simple things using this approach:
But with complex commands, that doesn't work either. I'm not sure yet what causes it to break.
I don't know your macro, of course, but you can get around the problem (also I don't know why it happens, perhaps @peternlewis can explain) by using two variables, and putting the pipes in the shell action itself.
var1=ps -A -o %cpu
var2=awk '{s+=$1} END {print int(0.5+s)}'
Shell command:
$KMVAR_var1 | $KMVAR_var2
Not as convenient as one variable, but it worked in my testing.
Ah yes, that's what I missed – or rather, forgot, while looking into this! [The quoted text is from before I swapped the ls and the cat – not that it's relevant anyway ].
I'll leave it to the experts and go to bed because my brain needs a reboot. Good luck!
That's a very good idea, but my macro is much more complicated than I've indicated so far.
I can't break up the command into subcommands because the macro that I'm writing is supposed to handle "unknown" command lines. It would be extremely difficult for my routine to "parse" input strings to find the pipe commands, and then pass each substring to a different shell. Not impossible, but I'm not Einstein. To prove my point, here's another one of the more difficult strings I wanted to process:
echo vm_stat | head -n 1 | tr -cd '[0-9]'\*vm_stat | head -n 2 | tail -n 1 | tr -cd '[0-9]' \/ 1024 \/ 1024 | bc
Notice how there are pipes within backquotes.(!!!) [EDIT: this command is so complicated that this website failed to handle it properly. I'll try to fix it.] This works from the command line. Why wouldn't it work in a shell, especially when it works in a file passed to a shell?
So I'm still trying to find out if a shell can receive a pipe character. I would be surprised if there was no way to do it.
EDIT: Here's a screen capture of some of the more complex commands I need to pass to a shell. In this screenshot you can see the pipes within backquotes.
Yea, that'd be tough! As I was intrigued by this, and had no clue what the issue was, I went searching for an answer. And I found one (an explanation, that is), and a fix that works perfectly, but the data source is one that isn't necessarily well received here, so I'll leave them anonymous for now. But they said this:
The issue arises because Keyboard Maestro's shell script action processes each variable as a separate entity and does not interpret the pipe (|) character as a command separator when it's part of a single variable. Instead, it treats the entire value of the variable as a single command argument, which is why it fails.
ㅤ
To address this, you can use the eval command in your shell script action to force the shell to interpret the contents of the variable as a command.
And indeed, that is the solution—this works perfectly with the last part of your complex example:
I never would have thought to try eval on my own, and my source's interpretation of the cause of the problem makes sense to me. But as with all answers, please evaluate against your own sources for confirmation. But eval definitely makes it work :).
I had to scream with laughter about 30 seconds after reading that comment, as it took me that long to realize who or what you were referring to there.
That's a stunning solution. I haven't tested it, but I believe you, that it works. I'm tempted to mark it as a solution even before I try it myself.
I'm working on a pretty cool utility. You can probably guess what data it might be dealing with from the sample code above. If this works, it will really help me out. Thainks.
I may be missing something in what you are attempting there, but remember, of course, that the With input from field just defines a string of characters passed into the command line, unevaluated, as STDIN
In bash, the only ways to generate, manipulate, or store code more complex than a simple command at runtime involve storing the code's plain text in a variable, file, stream, or function, and then using eval or sh to evaluate the stored code. Directly manipulating raw code strings is among the least robust of metaprogramming techniques and most common sources of bugs and security issues. That's because predicting all possible ways code might come together to form a valid construct and restricting it to never operate outside of what's expected requires great care and detailed knowledge of language quirks. Bash lacks all the usual kinds of abstractions that allow doing this safely. Excessive use can also obfuscate your code.
I'd never found a reason to use that setting, but that makes perfect sense, in theory. But I can't figure out how I'd use it even with the simple example @Airy started with, ls | cat. Looking at the example you linked, I think it'd be something like this:
Then it works as I'd expect based on what the linked thread explains. There's nothing in detail at all about "with input from" in the help for the shell script action, so I'm confused as to how to do this. The only way I made it work was basically as above: I eval the variable:
I understand that use, but I'm trying to understand how it could be used—as it seems it should work—to handle the case here, where he's trying to execute a string of two commands. I tried this, which seems the closest version of your wc example:
But nothing happened. Nothing happened if I left it blank, too, which also makes logical sense to my brain, but clearly is meaningless to the shell (and I understand why). So I'm still stuck trying to understand how pass input, which seems ideal in that it won't process the associated data before passing it to the shell, can be used here.
(I'm not saying this isn't a very neat feature, and I appreciate learning what it can do in KM, but I'm stumped by how to actually use it for this use case.)
Sorry for any confusion on my part. He just gave this as a simple example of a piped command set that fails in the Shell Script action:
ls | cat
Yes, it's a meaningless string of commands, but it does output a directory list in Terminal. But in the Shell Script action, the only way we found to get it working was by evaling the variable. I was thinking the pass input could work around that, based on how it's described?
Ah, OK, thanks. So then could that method be used for what he's trying to do? That is, he needs to get the output of some complicated connected commands, like the one he gave later: