Bash – Why does bash not assign a fd to var when using external_command {var}>somefile

bashio-redirection

According to the manpage of bash, "redirection operators may precede or appear anywhere within
a simple command or may follow a command."

Also according to the man page, "A simple command is a sequence of optional variable assignments
followed by blank-separated words and redirections, and terminated by
a control operator."

Now here is the problem, what exactly does the shell mean by a simple command and a command?
Because, /bin/echo foo {var}> somefile results in var not being assigned a fd.
In contrast to that, echo foo {var}> somefile does result in a fd being assigned to var.
It appears that, unlike with the command, it works with the builtin. I have noted that this kind of construction can appear anywhere in a command that uses a builtin: {var}> someFile echo foo works. In this context, if a simple command consists of builtins, therefore a word or a token is also a builtin? Because that's what the manpage says: "Word: A sequence of characters considered as a single unit by the
shell. Also known as a token.""

I have also observed that fds get assigned when used with builtins and not external commands.
So what is the problem here? Is my observation correct? And what exactly is the difference between a simple command and a command. The manpage is very vague about this.

Best Answer

From the top of my head, I'd guess it's because when running an external command, the shell 1) forks, 2) handles redirections in the child process, and 3) exec's the actual command. With the assignment to var done in 2, in the child, it's not visible to the parent shell after the launched program exits. With a builtin command, there's no fork, and the shell juggles the fd's as necessary in the main shell process, and the variable assignment takes effect there.

Not that that matters much, since the redirection in some external command {var}>/whatever is useless anyway. The external command can't know what fd was opened for it, and while it could determine what fd's it has, there might be others than the one opened for the redirection on this line, so it can't reliably use that fd to output to /whatever. Instead, you'd usually use a fixed fd number, or have some command line argument or environment variable to tell the fd number to use.

But you can't do that here either, since the variable isn't yet set when the expansions on the command line are processed so it's quite hard to pass it to the started program. unset var; /bin/echo "var=$var" {var}>/dev/null outputs just var= and so does unset var; var=$var /usr/bin/env {var}>/dev/null |grep ^var. (Though in ksh and zsh the latter seems to pass an actual number through the environment.)

The only place where that kind of redirection seems to make sense, is exec {var}>/whatever, and that being a builtin, the variable is set in the main shell, and the value is there for the following commands to use.

Related Question