Updated SC2005 (markdown)

Vidar Holen
2021-09-23 10:25:11 -07:00
parent 0c0db020bd
commit cdb853f51c

@@ -4,26 +4,46 @@ Useless `echo`? Instead of `echo $(cmd)`, just use `cmd`
### Problematic code: ### Problematic code:
```sh ```sh
echo "$(cat 1.txt)" echo "$(whoami)"
echo `< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6`
``` ```
### Correct code: ### Correct code:
```sh ```sh
cat 1.txt # In bash, but faster and still sticks exactly one newline: printf '%s\n' "$(<1.txt)" whoami
# The original `echo` sticks a newline; we want it too.
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6; echo
``` ```
### Rationale ### 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 ### 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)"
```