3

It seems that bash and zsh will perform the variable and arithmetic expansions in a child process when

a) they're following a redirection operator like <, >, >> or <<<.

b) the command they're part of is not a built-in or function.

bash -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
0
zsh -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
0

ksh -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
7

ksh above is like any other shell except bash or zsh.

This has nothing to do with those being arithmetic expansions: analogously, the same thing happens with

unset i; /bin/echo >${i:=7}.txt; echo $i

will only print 7 in shells other than bash or zsh.

However, as if this weren't bad enough, the behavior is not consistent in any fathomable way between bash and zsh:

bash -c 'i=0; command echo > $((i++)).txt; echo $i'
1
zsh -c 'i=0; command echo > $((i++)).txt; echo $i'
0

bash -c 'i=0; i=$i /usr/bin/printenv i > $((++i)).bash; echo $i; cat *.bash'
0
0
zsh -c 'i=0; i=$i /usr/bin/printenv i > $((++i)).zsh; echo $i; cat *.zsh'
0
1

So, my question is: What does the standard say? Is this acceptable?

I was able to find a lot about variable assignments as in KEY=val cmd and when they may or may not "affect the current execution environment", but nothing about the interaction between redirections, $-expansions and external commands.

And it could NOT be that it also applies to the variable assignments done as part of $-expansions, because ls $((i=2+3)) results in i being set to 5 in all the shells no matter if ls is an external command or a built-in.

  • Which ksh is that? ksh on macOS 10.14 (version sh (AT&T Research) 93u+ 2012-08-01) outputs 0 for the first test. – muru May 17 at 1:35
  • Version AJM 93u+ 2012-08-01 (Centos 7.6.1810). Also mksh (the shell from android), the /bin/ksh from OpenBSD, dash, etc. – Căcărău May 17 at 1:54
  • See also the differences in "$shell" -c 'i=0; echo "$((i+=1))" | "echo $((i+=1))"; echo "$i"'. That was discussed not so long ago on the austin-group ml, but I don't remember the details. – Stéphane Chazelas May 17 at 15:29
  • 2
    In zsh, command echo runs the echo command from the file system (unless in sh emulation) while in bash it runs the builtin echo (it would only bypass a echo function). – Stéphane Chazelas May 17 at 15:31
3

This is left unspecified, so each shell is allowed to do what it wants, and doesn't have to document the details. (From a historical perspective, it was unspecified because different shells did different things.) Technically a shell would be allowed to flip a coin. In practice, the details of what runs in a separate environment and what doesn't can depend on optimizations made in certain cases, e.g. depending on whether a command is a built-in, depending on whether a redirection is to/from /dev/null, depending on whether traps are active, depending on whether set -e is in effect, based on whether the command is the last one in a list, etc.

From SUSv4 (POSIX.1-2008) “Shell and Utilities” — §2.9.1 ”Simple Commands“:

If the command name is not a special built-in utility or function, the variable assignments shall be exported for the execution environment of the command and shall not affect the current execution environment except as a side-effect of the expansions performed in step 4. In this case it is unspecified:

  • Whether or not the assignments are visible for subsequent expansions in step 4

  • Whether variable assignments made as side-effects of these expansions are visible for subsequent expansions in step 4, or in the current shell execution environment, or both

To clarify: “step 4” includes parameter expansion (e.g. ${i:=7}) and arithmetic expansion (e.g. $((i=7))). The variable expansions that ”shall not affect the current execution environment“ are the ones at the front of the command, e.g. i=7 ls. So this paragraph says, among other things, that if a parameter expansion or a variable expansion modifies the value of a variable, it's unspecified whether this has an effect after the command returns.

In practice, shells usually apply a redirection to an external command by forking first and doing the redirection in the subshell. But they differ in whether they determine the target of the redirection before or after creating the subshell.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.