I used one of the following
echo $(stuff)
echo `stuff`
(where stuff is e.g. pwd or date or something more complicated).
Then I was told this syntax is wrong, a bad practice, non-elegant, excessive, redundant, overly complicated, a cargo cult programming, noobish, naive etc.
But the command does work, so what exactly is wrong with it?
Answer
A sole stuff would most probably work for you.
What happens
When you run foo $(stuff), this is what happens:
stuffruns;- its output (stdout), instead of being printed, replaces
$(stuff)in the invocation offoo; - then
fooruns, its command line arguments obviously depend on whatstuffreturned.
This $(…) mechanism is called "command substitution". In your case the main command is echo which basically prints its command line arguments to stdout. So whatever stuff tries to print to stdout is captured, passed to echo and printed to stdout by echo.
If you want the output of stuff to be printed to stdout, just run the sole stuff.
The `…` syntax serves the same purpose as $(…) (under the same name: "command substitution"), there are few differences though, so you cannot blindly interchange them. See this FAQ and this question.
Should I avoid echo $(stuff) no matter what?
There is a reason you may want to use echo $(stuff) if you know what you're doing. For the same reason you should avoid echo $(stuff) if you don't really know what you're doing.
The point is stuff and echo $(stuff) are not exactly equivalent. The latter means calling split+glob operator on the output of stuff with the default value of $IFS. Double quoting the command substitution prevents this. Single quoting the command substitution makes it no longer be a command substitution.
To observe this when it comes to splitting run these commands:
echo "a b"
echo $(echo "a b")
echo "$(echo "a b")" # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a b")'
And for globbing:
echo "/*"
echo $(echo "/*")
echo "$(echo "/*")" # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'
As you can see echo "$(stuff)" is equivalent(-ish*) to stuff. You could use it but what's the point of complicating things this way?
On the other hand if you want the output of stuff to undergo splitting+globbing then you may find echo $(stuff) useful. It has to be your conscious decision though.
There are commands generating output that should be evaluated (which includes splitting, globbing and more) and run by the shell, so eval "$(stuff)" is a possibility (see this answer). I have never seen a command that needs its output to undergo additional splitting+globbing before being printed. Deliberately using echo $(stuff) seems very uncommon.
What about var=$(stuff); echo "$var"?
Good point. This snippet:
var=$(stuff)
echo "$var"
should be equivalent to echo "$(stuff)" equivalent(-ish*) to stuff. If it's the whole code, just run stuff instead.
If, however, you need to use the output of stuff more than once then this approach
var=$(stuff)
foo "$var"
bar "$var"
is usually better than
foo "$(stuff)"
bar "$(stuff)"
Even if foo is echo and you get echo "$var" in your code, it may be better to keep it this way. Things to consider:
- With
var=$(stuff)stuffruns once; even if the command is fast, avoiding computing the same output twice is the right thing. Or maybestuffhas effects other than writing to stdout (e.g. creating a temporary file, starting a service, starting a virtual machine, notifying a remote server), so you don't want to run it multiple times. - If
stuffgenerates time-depending or somewhat random output, you may get inconsistent results fromfoo "$(stuff)"andbar "$(stuff)". Aftervar=$(stuff)the value of$varis fixed and you can be surefoo "$var"andbar "$var"get identical command line argument.
In some cases instead of foo "$var" you may want (need) to use foo $var, especially if stuff generates multiple arguments for foo (an array variable may be better if your shell supports it). Again, know what you're doing. When it comes to echo the difference between echo $var and echo "$var" is the same as between echo $(stuff) and echo "$(stuff)".
*Equivalent(-ish)?
I said echo "$(stuff)" is equivalent(-ish) to stuff. There are at least two issues that make it not exactly equivalent:
$(stuff)runsstuffin a subshell, so it's better to sayecho "$(stuff)"is equivalent(-ish) to(stuff). Commands that affect the shell they run in, if in a subshell, don't affect the main shell.In this example
stuffisa=1; echo "$a":a=0
echo "$(a=1; echo "$a")" # echo "$(stuff)"
echo "$a"Compare it with
a=0
a=1; echo "$a" # stuff
echo "$a"and with
a=0
(a=1; echo "$a") # (stuff)
echo "$a"Another example, start with
stuffbeingcd /; pwd:cd /bin
echo "$(cd /; pwd)" # echo "$(stuff)"
pwdand test
stuffand(stuff)versions.echois not a good tool to display uncontrolled data. Thisecho "$var"we were talking about should have beenprintf '%s\n' "$var". But since the question mentionsechoand since the most probable solution is not to useechoin the first place, I decided not to introduceprintfup until now.stuffor(stuff)will interleave stdout and stderr output, whileecho $(stuff)will print all the stderr output fromstuff(which runs first), and only then the stdout output digested byecho(which runs last).$(…)strips off any trailing newline and thenechoadds it back. Soecho "$(printf %s 'a')" | xxdgives different output thanprintf %s 'a' | xxd.Some commands (
lsfor example) work differently depending if the standard output is a console or not; sols | catdoes not the samelsdoes. Similarlyecho $(ls)will work differently thanls.Putting
lsaside, in a general case if you have to force this other behavior thenstuff | catis better thanecho $(ls)orecho "$(ls)"because it doesn't trigger all the other issues mentioned here.Possibly different exit status (mentioned for completeness of this wiki answer; for details see another answer that deserves credit).
No comments:
Post a Comment