mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-10-03 19:29:44 +08:00
Updated SC2005 (markdown)
38
SC2005.md
38
SC2005.md
@@ -4,26 +4,46 @@ Useless `echo`? Instead of `echo $(cmd)`, just use `cmd`
|
||||
### Problematic code:
|
||||
|
||||
```sh
|
||||
echo "$(cat 1.txt)"
|
||||
echo `< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6`
|
||||
echo "$(whoami)"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
```sh
|
||||
cat 1.txt # In bash, but faster and still sticks exactly one newline: printf '%s\n' "$(<1.txt)"
|
||||
# The original `echo` sticks a newline; we want it too.
|
||||
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6; echo
|
||||
whoami
|
||||
```
|
||||
|
||||
### Rationale
|
||||
|
||||
The command substitution `$(foo)` yields the result of command `foo` with trailing newlines erased, and when it is passed to `echo` it generally just gives the same result as `foo`.
|
||||
ShellCheck found the unnecessary construct `echo "$(somecommand here)"`.
|
||||
|
||||
The command `echo "$(false)"` will return true, whereas `false` of course returns false – beware of the ignored exit code before blindly altering scripts. If using `set -e`, the correct substitution of `echo "$(cmd)"` would be `cmd || true`.
|
||||
This is generally due to a misunderstanding about what `echo` does. It has no role in "showing on screen" or similar, but simply writes a string to standard output. This is also how all other programs output data.
|
||||
|
||||
`echo "$(somecommand)"` will capture the output `somecommand` writes to standard output and write it to standard output, where it was already going. At best this is a no-op, but it may have several other negative effects:
|
||||
|
||||
* It disables parallel processing in pipelines, such as `echo "$(find . -name '*.iso')" | xargs sha1sum` which does not allow iterating files and checksumming at the same time. Similarly, users don't see incremental updates as programs run.
|
||||
* It introduces shell and echo related pitfalls like being unable to output the string `-n`, stripping NUL bytes and trailing linefeeds, and expanding escape sequences in some shells but not others.
|
||||
* It suppresses the exit code of the command, so that `echo "$(grep '^user:' /etc/passwd)"` no longer returns with failure when the user is not found.
|
||||
* It does not allow programs to tailor their output for terminals, such as `ls` vs `echo "$(ls)"` where the former outputs columns and colors according to user preferences, while the latter doesn't.
|
||||
* It uses unnecessary memory to buffer up the data before writing it where it was already going.
|
||||
|
||||
To avoid all this, simply replace `echo "$(somecommand)"` with `somecommand` as in the example. It's shorter, faster, and more correct.
|
||||
|
||||
### Exceptions
|
||||
|
||||
One may want to use command substitutions plus `echo` to make sure there is exactly one trailing newline. The special command substitution `$(<file)` in `bash` is also un-outline-able.
|
||||
If you are relying on one of the otherwise detrimental effects for correctness, you can consider one of:
|
||||
|
||||
Anyway, echo is still not that reliable (see [[SC2039#echo-flags]]) and `printf` should be used instead.
|
||||
```
|
||||
# Suppress exit code without the other negative effects
|
||||
cmd || true
|
||||
|
||||
# Disable tty specific output without the other negative effects
|
||||
cmd | cat
|
||||
|
||||
# Buffer up potentially large output without using more memory or modifying the content in any way
|
||||
cmd > file.tmp
|
||||
cat file.tmp
|
||||
|
||||
# Exactly like `echo "$(cmd)"`, but allows output like `-n` and works the same across shells
|
||||
printf '%s\n' "$(cmd)"
|
||||
```
|
Reference in New Issue
Block a user