How does bash know about its parent's coprocess in this situation, and why does a shebang line change it?
outer.sh
:
ls -l /proc/$$/exe
coproc cat
./inner.sh
kill $!
inner.sh
:
ls -l /proc/$$/exe
set | grep COPROC || echo No match found
coproc cat
kill $!
When I run ./outer.sh
, this gets printed:
lrwxrwxrwx 1 joe joe 0 Jun 16 22:47 /proc/147876/exe -> /bin/bash
lrwxrwxrwx 1 joe joe 0 Jun 16 22:47 /proc/147879/exe -> /bin/bash
No match found
./inner.sh: line 3: warning: execute_coproc: coproc [147878:COPROC] still exists
Since COPROC
and COPROC_PID
aren’t set in the child, how does it know about the one from the parent to be able to give me that warning?
Also, I discovered that if I add #!/bin/bash
to the top of inner.sh
, or if I call bash ./inner.sh
instead of just ./inner.sh
from outer.sh
, then the warning goes away. Why does this change anything, since it’s getting ran with a bash subprocess either way?
A script without shebang is meant to be interpreted by a POSIX-compliant sh
interpreter. That’s actually the POSIX way to write POSIX scripts, POSIX doesn’t specify shebangs, though in practice using shebangs is more portable / reliable, and here is a good example why.
The bash shell is such a POSIX sh interpreter. bash (some versions and in some custom builds and in some environments) is actually the only FLOSS shell that I know that has been certified as compliant when running as sh
(not when running as bash
).
When executing a shebang-less script, bash, when execve()
returns ENOEXEC and after having checked that it doesn’t look like a binary file, interprets it in a child of his, simulating an execution by attempting to reset its state to the default.
That means however that that script when run from bash
is interpreted as a bash script instead of a POSIX sh script unless bash
is running in POSIX mode itself (such as when invoked as sh
itself).
$ cat a
alias uname='echo hi'
uname
$ zsh -c ./a
hi
$ sh ./a
hi
$ bash -c ./a
Linux
$ (exec -a sh bash -c ./a)
hi
See how a
was interpreted as bash
language (ignoring aliases) instead of the sh languages when invoked by bash
.
~$ strace -qqfe execve bash -c ./a
execve("/usr/bin/bash", ["bash", "-c", "./a"], 0x7fff0081a820 /* 66 vars */) = 0
execve("./a", ["./a"], 0x55b18b3a4660 /* 66 vars */) = -1 ENOEXEC (Exec format error)
[pid 123559] execve("/usr/bin/uname", ["uname"], 0x55b18b3a4660 /* 66 vars */) = 0
Linux
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=123559, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
See how bash
didn’t execute sh
to interpret the script.
The fact that you get that warning: execute_coproc: coproc [147878:COPROC] still exists
is a bug whereby bash
fails to reset its state properly.
In any case, coproc
is not a sh
keyword so doesn’t have its place in a shebang-less script. coproc
is from zsh
(while coprocesses are from ksh), though bash
‘s implementation is completely different, so you should have a #! /bin/bash -
shebang here.
With bash ./inner.sh
or with a shebang, there is a proper execution of a new interpreter instance, and execve()
completely and correctly wipes the process memory.