Bash "swallowing" child processes of subordinate shells when executing a single command

Including bash / sh in unexpected behavior, and I wonder if someone can explain the rationale for this question and give a solution to the question below.

In an interactive bash shell session, I do:

$ bash -c 'sleep 10 && echo'

With ps on Linux, it looks like this:

\_ -bash \_ bash -c sleep 10 && echo \_ sleep 10

The process tree is what I would expect:

  • My bash shell interactive process ( $ )
  • Shell process for children ( bash -c ... )
  • sleeping baby process

However, if part of the command of my bash -c is a single command, for example:

$ bash -c 'sleep 10'

Then the middle sub-shell is swallowed, and my interactive terminal session is performing โ€œdirectlyโ€ when the children process the process. The process tree is as follows:

\_ -bash \_ sleep 10

So, from the perspective of the process tree, these two results give the same result:

  • $ bash -c 'sleep 10'
  • $ sleep 10

What's going on here?

Now to my question: is there a way to force an intermediate shell, regardless of the complexity of the expression passed to bash -c ... ?

(I could add something like ; echo; to my actual command and that "works", but I would prefer. Is there a better way to make the intermediate process exist?)

(edit: typo in ps output, remove the sh tag, as mentioned in the comments, another typo)

+7
linux bash shell subshell
source share
1 answer

There is actually a comment in the bash source that describes most of the rationale for this function:

 /* If this is a simple command, tell execute_disk_command that it might be able to get away without forking and simply exec. This means things like ( sleep 10 ) will only cause one fork. If we're timing the command or inverting its return value, however, we cannot do this optimization. */ if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) && ((tcom->flags & CMD_TIME_PIPELINE) == 0) && ((tcom->flags & CMD_INVERT_RETURN) == 0)) { tcom->flags |= CMD_NO_FORK; if (tcom->type == cm_simple) tcom->value.Simple->flags |= CMD_NO_FORK; } 

In the case of bash -c '...' the CMD_NO_FORK flag CMD_NO_FORK set if the should_suppress_fork function is defined in builtins/evalstring.c .

always to let the shell do this. This only happens when:

  • The input is from a string string, and the shell is in the last command on that string.
  • After the team completes further commands, traps, interceptors, etc. not.
  • The exit status does not have to be inverted or otherwise changed.
  • No redirection required.

This saves memory, makes the process start time a little faster (since it should not be fork ed), and ensures that the signals passed to your PID are directed directly to the process you are working in, allowing the parent element sh -c 'sleep 10' determine exactly which signal was killed by sleep , if it is actually killed by the signal.

However, if for some reason you want to block it, you need to set a trap - any trap will do:

 # run the noop command (:) at exit bash -c 'trap : EXIT; sleep 10' 
+4
source share

All Articles