Shell语法${1:-$(</dev/stdin)}的底层机制解析与疑问
${1:-$(</dev/stdin)} Bash Trick First, let's recap the clever function you're working with—this is hands down one of the most concise ways to make a bash function accept both command-line arguments and piped input:
log(){ echo -e >&1 "INFO: ${1:-$(</dev/stdin)}" }
As you noted, it works seamlessly for both log "Something went wrong" (using the provided argument) and some-command | log (using piped STDIN). Let's dive deep into your questions to unpack how this magic works.
1. What's happening under the hood when we use $(</dev/stdin) (no command before the redirect)?
Great catch—this is a bash-specific optimization that's easy to overlook. Normally, you might write $(cat </dev/stdin) to read STDIN into a command substitution, but bash has a built-in shortcut: when you write $(< filename), it directly reads the contents of filename without spawning an external process (like cat).
Here's the play-by-play:
- When bash sees
$(</dev/stdin), it recognizes this as a special case. - It opens
/dev/stdin(a file descriptor pointing to the current standard input of the function's subshell) and reads all content until EOF. - It then replaces the command substitution with that content as a raw string.
This isn't just cleaner—it's faster too, since it avoids the overhead of forking a separate cat process. Note that this is a bash-exclusive feature (not POSIX sh), so it won't work in shells like dash.
2. Why wrap the STDIN redirect in command substitution?
The ${VAR:-FALLBACK} syntax expects FALLBACK to be a string value, not a shell operation. Let's break down what happens if we skip the command substitution:
- If you wrote
${1:-</dev/stdin}, bash would treat</dev/stdinas literal text. Your function would outputINFO: </dev/stdininstead of reading actual input. - By wrapping the redirect in
$(), we convert the act of reading STDIN into a string that can be used as the fallback value. The command substitution captures the output of the redirect operation (the content of STDIN) and plugs it into the variable expansion.
In short: command substitution turns a file-read operation into a usable string for the fallback logic.
3. Is this syntax safe? When might it fail?
For most common use cases, this is safe and reliable—but there are edge cases to watch out for:
Safe scenarios (when it works well)
- Multi-line input: Since you're using double quotes around the expansion (
"INFO: ${1:-...}"), newlines in the STDIN content are preserved. Without quotes, bash would split the input into words and collapse whitespace, but your example avoids this. - Special characters: As long as you keep the double quotes, characters like
$,*, or spaces are treated literally—bash won't try to expand them. The$(</dev/stdin)trick reads content as-is, it doesn't execute any commands embedded in the input.
Edge cases where it might fail or behave unexpectedly
- Both arguments and STDIN are provided: If you run
echo "Piped text" | log "Argument text", the function will use the argument (Argument text) and completely ignore the piped input. This is intentional per the${1:-...}logic, but it's a gotcha if you expect both inputs to be used. - Large input: Command substitution reads all input into memory. If you're piping a huge file (like multi-GB), this could consume excessive memory or even crash the shell.
- Non-interactive STDIN closure: If the STDIN file descriptor is closed (e.g., in some background processes or scripts),
$(</dev/stdin)will fail silently and return an empty string. - Interactive input hangs: If you run
logwithout arguments and without piping input, the function will hang waiting for you to enter text (until you press Ctrl+D to send EOF). This might be unexpected if users expect an error message instead. - POSIX compatibility: As mentioned earlier, this trick relies on bash's
$(< filename)shortcut. It won't work in POSIX-compliant shells like dash, so avoid it if your script needs to be portable beyond bash.
内容的提问来源于stack exchange,提问作者Joe Healey

