Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The mechanics of this shell syntax: ${1:-$(</dev/stdin)}

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 process command substitution
  • and finally, </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:

  1. I've never seen a redirect (<) inside a process command substitution, without an actual command preceding it (e.g. $(cat < somefile.ext)). So what is actually happening (the nitty-gritty) when the process command substitution receives the redirect with no other command to invoke?
  2. Why is it necessary to wrap the STDIN redirect in a process command substitution at all? (actually, as I write this it occurs to me I haven't tested it without, but I'll keep it simple).
  3. Is this safe? I've used it with multiline STDIN, and it hasn't broken so far. Where might this fall down (if anywhere?).
like image 320
Joe Healey Avatar asked Jan 30 '19 09:01

Joe Healey


People also ask

What is ${ 1 :-/ dev Stdin?

${1:-$(</dev/stdin)}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: I've never seen a redirect ( < ) inside a process command substitution, without an actual command preceding it (e.g. $(cat < somefile.

What is Stdin in shell script?

In Linux, stdin is the standard input stream. This accepts text as its input. Text output from the command to the shell is delivered via the stdout (standard out) stream. Error messages from the command are sent through the stderr (standard error) stream.

Does echo print to Stdin?

The output echo generates is written in the stdout stream. Then, the piping redirects the content of stdout to stdin for the grep command. That's how grep knows what content to perform the operation on. If you want to pipe both the stderr and stdout to the next command, then use the “|&” instead.


2 Answers

  1. $(..): from bash manual is a command substitution not process substitution <(..). and from Command substitution

The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

  1. /dev/stdin is a symlink to /proc/self/fd/0, convenient here because of $(<..) syntax which expects a file.

  2. 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.

like image 118
Nahuel Fouilleul Avatar answered Oct 16 '22 02:10

Nahuel Fouilleul


Answering your questions

  • You have confused the syntax between using process substitution which takes the syntax of <(cmd) and simple shell re-direction < file. In plain form, < basically is to read from the standard input and > to write to standard output. The syntax < file is a short-hand syntax to put the contents of the file made available on the standard input which can be read by the commands.
  • So when you run cat < file, it basically puts the content of the file in standard input file descriptor which is later than read by the cat process. The advantage of using $(<file) would be the shell doesn't have fork an external process cat and just use its own mechanism to read the file content.
  • The $(..) is a syntax for command-substitution where command is run in a sub-shell environment and $(..) is replaced with the standard output of the command. For a special case of "$(<file)" i.e. without any commands and only re-directions involved, the shell instead of reading from the standard input starts reading from the start of the file and puts the result on the standard output.
like image 22
Inian Avatar answered Oct 16 '22 01:10

Inian