5

Consider the following commands:

exit > /dev/null
exit | cat

On a few shells (ksh, bash, (d)ash), the behavior is the same: The first command causes the shell to quit immediately, while the second one has no visible behavior.

I concluded that the first command did not involve a fork(2), while the second command involved two (one to execute exit, the other execve(2)s to cat).

I looked at the POSIX specification section 2.14, but didn't find anything explicitly stating this.

Is it specified by POSIX that the some commands must not fork(2) while some others must? Is spawning a subshell for the first command acceptable by the standard?


I am aware that ( exit ) should never exit the current shell, because the parentheses spawns a sub-shell that actually executes the exit command, which means the sub-shell will exit immediately. However, I am unsure about redirections and piping, since there's no parenthesis here.

I am asking this question because in a recent course lab, we're told to implement a minimal Unix shell. There are many "optional features" that we can implement, for additional points. One of those "optional features" is combined redirection and pipelining. We had a few different implementations and I believe some are closer to the specified behavior of POSIX than others, and therefore I'd like to know how the actual behavior is specified.

iBug
  • 3,508

2 Answers2

3

Well (exit),

The exit utility shall cause the shell to exit from its current execution environment [...]

and (2.12. Shell Execution Environment)

A subshell environment shall be created as a duplicate of the shell environment, [...] Additionally, each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment. All other commands shall be executed in the current shell environment.

So the exit in a pipeline runs in an execution environment/subshell of its own, and exits only that, while the one in the simple command exit > /dev/null runs in the main shell environment. (As noted in the comments, the redirection doesn't really affect it at all.)

Note that the part in the middle of the second quote means that some shell might run all the commands of a pipeline in the main environment, thus exiting the whole shell even in that case. In practice, that's more commonly done for the last command of a pipeline.

In Bash with lastpipe, for example:

$ bash -c 'true | exit; echo end.'
end.

But

$ bash -O lastpipe -c 'true | exit; echo end.'

doesn't print anything.

ilkkachu
  • 138,973
  • Is it specified whether exit > /dev/null shall be executed in the current shell? Is spawning a sub-shell for the command acceptable? – iBug Apr 26 '19 at 16:39
  • @iBug, I think that's the "All other commands shall be executed in the current shell environment" part. Plus there's the thing that if a simple command would be executed in a subshell, no variable assignments would ever do anything (the new values would be lost with the subshell). – ilkkachu Apr 26 '19 at 16:47
0

This occurs because exit is executed in a subshell for exit | cat and not for exit > /dev/null. Exiting from a subshell does not exit from the main shell:

If the current execution environment is a subshell environment, the shell shall exit from the subshell environment with the specified exit status and continue in the environment from which that subshell environment was invoked

But the specific difference of subshell or not subshell is specified in section 2.12 of the POSIX standard – to quote the relevant passage in full:

A subshell environment shall be created as a duplicate of the shell environment, except that signal traps that are not being ignored shall be set to the default action. Changes made to the subshell environment shall not affect the shell environment. Command substitution, commands that are grouped with parentheses, and asynchronous lists shall be executed in a subshell environment. Additionally, each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment. All other commands shall be executed in the current shell environment.

Here exit | cat fits the description:

each command of a multi-command pipeline is in a subshell environment

And therefore will usually be executed in a subshell. However this can catch you out:

as an extension, however, any or all commands in a pipeline may be executed in the current environment

... Meaning it's not guaranteed in all shells. I have previously had to debug code where one implementation executed the right hand side of a pipe in the current shell, allowing the following to work on some implementations of KSH but not others:

cat foo | while read line
do
    X="$line"
done

So always assume that a pipe might spawn a subshell, but don't rely on it.