ls -l /proc/$$/exe
coproc cat
kill $!


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?

1 Answer 1


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'
$ zsh -c ./a
$ sh ./a
$ bash -c ./a
$ (exec -a sh bash -c ./a)

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
--- 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.

  • One thing I don't understand here: if executing a script with no shebang line means to run it as a POSIX script, then why does running ./a print Linux instead of hi? Jun 17, 2022 at 13:46
  • 1
    @JosephSible-ReinstateMonica, that's the whole point in my answer, bash is not POSIX compliant in that regard when not in POSIX mode. If your interactive shell is bash, bash will interpret it itself instead of invoking sh on it, and unless your bash was invoked as sh or with POSIXLY_CORRECT in the environment, it will interpret it in a non-POSIX way. In effect, the script will be interpreted as if it was written in the bash language instead of the sh language. Jun 18, 2022 at 14:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .