Reading — step 1 of 7
Learn
~2 min readCommand Substitution and Exit Status
Command substitution captures the OUTPUT of a command into a variable or argument. The Bash Reference Manual treats it as foundational — it's how shell scripts compose programs.
Two syntaxes
# Modern preferred:
date_str=$(date +%Y-%m-%d)
echo "Today is $date_str"
# Legacy backtick form (avoid):
date_str=`date +%Y-%m-%d`
$(...) is preferred because:
- Easier to read
- Easier to nest:
$(echo $(date)) - Backticks need escaping inside backticks
- Tools / linters expect
$(...)
Capturing output
file_count=$(ls /etc | wc -l)
echo "Found $file_count files"
user=$(whoami)
echo "Running as $user"
# Multiple words:
files=$(ls *.txt)
for f in $files; do # WORD-SPLIT — fragile
echo "$f"
done
Quoting matters
# UNQUOTED — word-splits and globs the result:
for f in $(ls *.txt); do echo "$f"; done
# Breaks if filenames have spaces, *, ?, or newlines
# QUOTED — preserves whole output as single string:
output="$(ls *.txt)"
echo "$output"
# For loops over files: use globs, not ls:
for f in *.txt; do echo "$f"; done
The for f in $(ls) pattern is a common trap — bash splits on IFS (whitespace) and globs. Use globs directly instead.
Trailing newlines
Command substitution STRIPS trailing newlines:
echo "hello
" | wc -c # 8 (hello + 2 newlines = 7 bytes, + echo's own trailing newline)
result=$(echo "hello
")
echo ${#result} # 5 ("hello" — newlines stripped)
Useful 99% of the time; surprises if you wanted to preserve trailing whitespace.
Capturing both stdout and stderr
# Combine stderr into stdout:
output=$(some_command 2>&1)
# Capture stderr separately (bash 4+):
output=$(some_command 2> /tmp/stderr.log)
err=$(<"/tmp/stderr.log")
Process substitution — different beast
# diff two command outputs without temp files:
diff <(ls /etc) <(ls /tmp)
# `<(cmd)` looks like a file containing cmd's output
Process substitution gives you a file-like reference to the output. Different from command substitution which gives you the OUTPUT string. Both useful, different tools.
Common mistakes
- Unquoted command substitution — word splits + globs the output. Quote with
"$(...)"unless you want splitting. - Backticks in production scripts — hard to read, hard to nest. Use
$(...). - *for f in $(ls .txt) — fragile. Use globs:
for f in *.txt. - Forgetting trailing newlines are stripped — they always are. If you need them, store length first.
- Capturing huge outputs — bash holds the whole thing in memory. For multi-GB outputs, pipe to a file or process line-by-line with
while read.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…