Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Odd behaviour in bash (and possibly other shells?)

Tags:

linux

bash

shell

When I do:

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

The output I get is:

cat/proc/25050/cmdline

Whereas the output I expected was:

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

On the other hand when I do:

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

I get the expected output, which is:

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

It seems like $$ is cat's pid rather than bash/sh's pid.
Why is this?
Does the shell do some kind of parsing and execve() style replace? If so, how does it know cat's PID before it even does the replace?

like image 617
ffledgling Avatar asked Sep 05 '16 14:09

ffledgling


1 Answers

In order to understand this behaviour, one has to figure how bash executes commands passed to it on the command line. The key point is that if the command is simple enough, there's no fork (or clone or anything 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 +++
$

It seems like $$ is cat's pid rather than bash/sh's pid.

It's actually both. bash execves cat directly, so one becomes the other.

To understand what exactly is needed for the no-fork behaviour, we need to look at the source code. There's 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's return status is not being inverted
       * THEN
       *   tell the execution code that we don't need to fork
       */

Source

like image 133
n. 1.8e9-where's-my-share m. Avatar answered Sep 28 '22 08:09

n. 1.8e9-where's-my-share m.