Executing a piped command in a subshell doesn't work if the command is in a variable
bash, pipe
Solution
If you like living dangerously, the answer is `eval`:
#!/bin/bash
COMMAND="pwd | awk '{print \"dir: \"\$1}'"
while true
do
echo $(eval $COMMAND)
sleep 1
done
Why dangerously? It's hard to control what's going on with `eval`, doubly so if you accept the command string from a naïve (let alone malicious) user. Also note how carefully I had to quote the string containing the command.
Why didn't version 2 in the question work?
COMMAND=pwd | awk '{print "dir: "$1}'
This line doesn't do what you think it does, put bluntly.
It is a pipeline, with the first 'command' being the string assignment `COMMAND=pwd` treated as an environment variable for the empty command, which sends no data to `awk`. After completion, the value in `$COMMAND` is an empty string (both because of the sub-shell used with the pipeline and because the `COMMAND=pwd` only applied while the (empty) command was executed), so the loop echoes the result of executing the empty string — which is also an empty string. Hence the line of blanks.
This works for me but I'm having an issue with adding a tab in the AWK printout, and I think it's because of the escaping. If I have `COMMAND="ls -l | awk '{print \$8 \$9}'"`, how would I insert a tab `\t` between the `$8` and `$9`?
Ouch, and Yuck!
If you had a tab in the source between `$8` and `$9`, `awk` would treat it as generic white space and paste `$8` and `$9` together in the output. To get a tab in the output, you'll need either a quoted string containing a tab, or a quoted string containing `\t` in between the two fields, or you'll use a `printf()` with a suitable format string.
So, in principle, you'll probably want:
COMMAND='ls -l | awk '\''{printf "%s\t%s\n", $8, $9}'\'
I've switched to single quotes around the string because they're mostly easier to understand. Whether you use single or double quotes, the command itself contains both, so you have to worry about how to do the escaping. The `'\''` sequence is how you embed a single quote into a single quoted string; the first single quote ends the current single quoted string; the backslash single quote adds a single quote, and the last single quote restarts a single string. The sequence `'\'` at the end could also be written `'\'''`, but the last two quotes are a zero-length single quoted string, so I dropped them.
This produces the nice output with tabs; you can verify by running:
eval $COMMAND
The `echo $(eval $COMMAND)` completely undoes all the careful spacing, flattening the entire list of files and times into a single line with each 'word' separated from the next by spaces.
You can work around that issue by forcing `echo` to respect the spacing (and newlines) with:
echo "$(eval $COMMAND)"
but this raises the question of 'why not use just `eval $COMMAND`?'
(Testing: bash 3.2.48 Mac OS X 10.7.4.)
Problem
The following code works fine ``` #!/bin/bash while true; do echo $(pwd | awk '{print "dir: "$1}') sleep 1 done ``` But I need the command to be in a variable, like this. ``` #!/bin/bash COMMAND=pwd | awk '{print "dir: "$1}' while true; do echo $($COMMAND) sleep 1 done ``` The first snippet executes fine, and prints something like `dir: /present/directory`. The second snippet just prints blank lines. Why is that and how do I fix it? There's no significance in the commands I used. I just needed to use a pipe and chose these commands to demonstrate it.