I recently came across this really neat syntax, in the context of creating bash
functions which can accept an argument or a stream from STDIN (i.e. can be piped to). On the surface, I understand what's happening here, but I'd like a bit more explanation of the actual mechanics of how this is working.
Here's the syntax (as per the title): ${1:-$(</dev/stdin)}
And in context, one might use it as:
log(){
echo -e >&1 "INFO: ${1:-$(</dev/stdin)}"
}
Allowing the following usage:
$ log foo
INFO: foo
Alternatively, you could also do this
mv -v foo.ext bar.ext | log
INFO: renamed 'foo.ext' -> 'bar.ext'
This is great because its the single most concise method I've seen for enabling both argument and pipe functionality with bash
functions (I forget where I came across it now, unfortunately).
Now, I understand (or think I understand), most of what's happening here at least superficially, but I'd appreciate a deeper understanding. Here's how I interpret it, followed by my remaining questions:
${1:-$(</dev/stdin)}
${1}
is obviously the default argument that a function accepts${1:-x}
is a variable/brace expansion 'fall back' to the string 'x'
if $1
is otherwise empty (or unset?). In this case falling back to the STDIN process sub.$()
is obviously a </dev/stdin
is obviously a redirect from standard input which allows the pipe to work at all.This essentially says if $1
isn't populated by an argument, fall back to using STDIN - which I'm happy with conceptually.
So here are my questions:
<
) inside a $(cat < somefile.ext)
). So what is actually happening (the nitty-gritty) when the $(
..)
: from bash manual is a command substitution not process substitution <(
..)
. and from Command substitutionThe command substitution $(cat file) can be replaced by the equivalent but faster $(< file).
/dev/stdin
is a symlink to /proc/self/fd/0
, convenient here because of $(<
..)
syntax which expects a file.
this could cause a problem because the command may be blocked until stdin closed. it is safe in the meaning that multiline input will be preserved because au double-quotes.
Finally creating a pipe and forking a process (like in mv -v foo.ext bar.ext | log
) for each log command may be inefficient.