Odd behavior in bash (and possibly in other shells?)

When I do this:

/bin/bash -c 'cat /proc/$$/cmdline' 

The output I get is:

 cat/proc/25050/cmdline 

While the expected result was as follows:

 /bin/bash -c 'cat /proc/$$/cmdline' 

On the other hand, when I do:

 /bin/bash -c 'echo $$; cat /proc/$$/cmdline' 

I get the expected result:

 28259 /bin/bash-cecho $$; cat /proc/$$/cmdline 

$$ seems to be cat pid, not bash / sh pid.
Why is this? Does the shell replace some sort of parsing and execve() style? If so, how does he know cat PID before he even replaces it?

+6
source share
1 answer

To understand this behavior, you need to understand how bash executes the commands passed to it on the command line. The key point is that if the command is simple enough, there is no fork (or clone or something like that).

 $ strace -f -e clone,execve /bin/bash -c 'cat /proc/$$/cmdline' execve("/bin/bash", ["/bin/bash", "-c", "cat /proc/$$/cmdline"], [/* 80 vars */]) = 0 execve("/bin/cat", ["cat", "/proc/2942/cmdline"], [/* 80 vars */]) = 0 cat/proc/2942/cmdline+++ exited with 0 +++ $ 

OTOH, if the command is more complicated, bash forks:

 $ strace -f -e clone,execve /bin/bash -c 'echo $$; cat /proc/$$/cmdline' execve("/bin/bash", ["/bin/bash", "-c", "echo $$; cat /proc/$$/cmdline"], [/* 80 vars */]) = 0 2933 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff64e6779d0) = 2934 Process 2934 attached [pid 2934] execve("/bin/cat", ["cat", "/proc/2933/cmdline"], [/* 80 vars */]) = 0 /bin/bash-cecho $$; cat /proc/$$/cmdline[pid 2934] +++ exited with 0 +++ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2934, si_uid=1000, si_status=0, si_utime=0, si_stime=0} --- +++ exited with 0 +++ $ 

$$ seems to be cat pid, not bash / sh pid.

In fact, both. bash execve cat , so everyone becomes different.

To understand what exactly is needed for the no-fork behavior, we need to look at the source code. Here is this comment:

  /* * IF * we were invoked as `bash -c' (startup_state == 2) AND * parse_and_execute has not been called recursively AND * we're not running a trap AND * we have parsed the full command (string == '\0') AND * we're not going to run the exit trap AND * we have a simple command without redirections AND * the command is not being timed AND * the command return status is not being inverted * THEN * tell the execution code that we don't need to fork */ 

Source

+4
source

All Articles