Reading — step 1 of 5
Read
~2 min readBuilt-ins & Variables
Command Substitution: $(cmd) and backticks
Command substitution captures the stdout of a command and splices it into the current command line.
today=$(date +%Y-%m-%d) # modern form
today=`date +%Y-%m-%d` # legacy backtick form
files=$(ls *.py | head -5) # full pipelines allowed
echo "$(uname) on $(hostname)"
$() vs backticks
| Property | $(cmd) | `cmd` |
|---|---|---|
| Nesting | trivial: $(a $(b)) | painful: `a \`b\ `` (must escape) |
| Quoting | inner quotes are normal | inner quotes interact with backslash rules |
| Recommended? | Yes, POSIX since 1992 | Legacy, still works |
POSIX shells, bash, zsh, and dash all support $(). There's no reason
to write backticks in new code, but you'll read them in older scripts.
How the shell implements it
- Parse phase: see
$(, find the matching)(handling nesting, quotes, and heredocs), capture the inner string as a "command". - Execution phase (before the surrounding command runs):
pipe(2)— create a pipe.fork(2)— child runs the inner command with stdoutdup2-ed to the pipe's write end.- Parent reads the pipe to EOF and
waitpids for the child.
- Word splitting & trimming:
- All trailing newlines are stripped (but not leading whitespace).
- The remaining bytes are subject to word splitting on
$IFSif the substitution is unquoted. - Inside
"...", the result is one literal string (no splitting).
$ touch "a b.txt"
$ for f in $(ls); do echo "[$f]"; done # WRONG: splits "a b.txt"
[a]
[b.txt]
$ for f in "$(ls)"; do echo "[$f]"; done # ALSO WRONG: one giant token
[a b.txt]
$ ls | while read -r f; do echo "[$f]"; done # OK
[a b.txt]
Exit status
The exit status of $(cmd) is the exit status of the inner command, but
in the surrounding context it's only the substituted bytes that flow
out — the exit code is lost unless you check $? immediately or store
the substitution result in a variable assignment.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…