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?
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
execve
s 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With