4

This question already has an answer here:

Why does this difference matter? The two blocks in the code below differ by the last line:

#!/bin/bash
if [[ -n "$1" ]]; then
    sleep 1 &
    p=$!
    kill $p &> /dev/null
else
    sleep 1 &
    p=$!
    kill $p &> /dev/null
    /bin/true # This line is the sole difference.
fi

Name it a.sh, and I get (on my Linux box)

$ ./a.sh
/a.sh: line 16: 18103 Terminated              sleep 1
$ ./a.sh foo
$ # no "Terminated" message

Why no message in the second case? Bash's basic behavior is to print "Terminated" (see this queostion).

(NB I use p=$! in my true code, but in the above case you can use kill $!.)

EDIT: Sergiy Kolodyazhnyy kindly referred to the question "Why are true and false so large?" in a comment. (It's rather a digression, but fun reading.) Unfortunately, the answer that comment belonged to was deleted, so I here record it. Thanks Sergiy.

marked as duplicate by Isaac, Romeo Ninov, Yaron, G-Man, thrig May 28 '18 at 18:55

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Huh, interesting. It also doesn't print anything in either case if you replace /bin/true with just true (tested with kill $! rather than using a variable). – l0b0 May 27 '18 at 6:27
  • @Isaac: No. Read the mentioned question carefully. And its answers do not answer my question. The answer to my question is: Because the shell exits (this point is lacking there, and answered for the first time in the answer below by Sergiy.) before checking the child process. – teika kazura May 28 '18 at 7:24
  • @Issac BTW your comment sheds some light on the community's readership: Three people upvoted your comment, rushing without understanding. ;-) – teika kazura May 28 '18 at 7:25
  • @teikakazura First: My name is Isaac, please. Second, the last line of this answer say: the previous explanation holds (the process is not dead yet). So, there you have it: It is a problem of timing, nothing else. And third: Now it is five (not new) users that think your question is a duplicate. Whaich does not erase the question, just add a link to another answer. – Isaac May 29 '18 at 5:44
  • @Isaac I'm sorry by calling you wrongly. The rule is to copy by dragging. (Never be lazy.) As for the third point, well, your comment is gone now... – teika kazura Jun 6 '18 at 12:56
3

As per Matteo Italia's answer:

Doing the same experiment with killall, instead, usually yields the "killed" message immediately, sign that the time/context switches/whatever required to execute an external command cause a delay long enough for the process to be killed before the control returns to the shell.

In other words, the delay caused by calling the external /bin/true cause a delay, which allows shell to print the message.

I've also performed tests with /bin/echo vs echo:

#!/bin/bash
if [[ -n "$1" ]]; then
    sleep 1 &
    p=$!
    kill $p &> /dev/null
    /bin/echo "a line"
else
    sleep 1 &
    p=$!
    kill $p &> /dev/null
fi

With this script:

$ bash ./mystery.sh 
$ bash ./mystery.sh foo
a line
./mystery.sh: line 11: 10361 Terminated              sleep 1

With built-in echo:

$ bash ./mystery.sh 
$ bash ./mystery.sh foo
a line

In other words, the fact that there is an external executable being called, forces the shell to perform check for child processes and background jobs when the last child returns. In case of:

if [[ -n "$1" ]]; then
    sleep 1 &
    p=$!
    kill $p &> /dev/null

from your original script there's no extra command being called, only last built in.


Among other things, I've performed a few tests with strace:

It seems that the parent process exits and doesn't wait for the child. In other words, parent process of the shell exits too early to perform an explicit check.

$ strace -s 1024 -e kill bash mystery.sh 
kill(9830, SIGTERM)                     = 0
mystery.sh: line 11:  9830 Terminated              sleep 1
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=9830, si_uid=1000, si_status=SIGTERM, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
$ strace -s 1024 -e kill bash mystery.sh  foo
kill(9839, SIGTERM)                     = 0
+++ exited with 0 +++

Notably, in tracing with positional parameter, the wait call is also absent:

$ strace -s 1024 -e kill,wait4 bash mystery.sh  foo
kill(9910, SIGTERM)                     = 0
+++ exited with 0 +++
$ strace -s 1024 -e kill,wait4 bash mystery.sh
kill(9916, SIGTERM)                     = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=9916, si_uid=1000, si_status=SIGTERM, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], WNOHANG, NULL) = 9916
wait4(-1, 0x7ffe8e5bb110, WNOHANG, NULL) = -1 ECHILD (No child processes)
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 9917
mystery.sh: line 11:  9916 Terminated              sleep 1
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9917, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffe8e5bb250, WNOHANG, NULL) = -1 ECHILD (No child processes)
+++ exited with 0 +++

Not the answer you're looking for? Browse other questions tagged or ask your own question.