3

I have the following in a script:

yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
ps aux | grep yes

When I run it, the output shows that yes is still running by the end of the script. However, if I run the commands interactively then the process terminates successfully, as in the following:

> yes >/dev/null &
[1] 9967
> kill -INT 9967
> ps aux | grep yes
sean ... 0:00 grep yes

Why does SIGINT terminate the process in the interactive instance but not in the scripted instance?

EDIT

Here's some supplementary information that may help to diagnose the issue. I wrote the following Go program to simulate the above script.

package main

import (
    "fmt"
    "os"
    "os/exec"
    "time"
)

func main() {
    yes := exec.Command("yes")
    if err := yes.Start(); err != nil {
        die("%v", err)
    }

    time.Sleep(time.Second*2)

    kill := exec.Command("kill", "-INT", fmt.Sprintf("%d", yes.Process.Pid))
    if err := kill.Run(); err != nil {
        die("%v", err)
    }

    time.Sleep(time.Second*2)

    out, err := exec.Command("bash", "-c", "ps aux | grep yes").CombinedOutput()
    if err != nil {
        die("%v", err)
    }
    fmt.Println(string(out))
}

func die(msg string, args ...interface{}) {
    fmt.Fprintf(os.Stderr, msg+"\n", args...)
    os.Exit(1)
}

I built it as main and running ./main in a script, and running ./main and ./main & interactively give the same, following, output:

sean ... 0:01 [yes] <defunct>
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

However, running ./main & in a script gives the following:

sean ... 0:03 yes
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

This makes me believe that the difference has less to do on Bash's own job control, though I'm running all of this in a Bash shell.

  • Unable to replicate; behavior was identical when interactive or run in a script in my test. – DopeGhoti Jun 21 '17 at 17:57
  • Able to replicate here - yes terminates immediately with interactive kill but stays running with the script. – Joe P Jun 21 '17 at 18:01
  • 1
    What shell are you using? – thrig Jun 21 '17 at 18:02
  • Replicated with GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu) - what shell and version do you have? – Joe P Jun 21 '17 at 18:02
  • The scripted instance is running the background command with SIGINT and SIGQUIT ignored for some reason. – PSkocik Jun 21 '17 at 18:25
5

What shell is used is a concern as different shells handle job control differently (and job control is complicated; job.c in bash presently weighs in at 3,300 lines of C according to cloc). pdksh 5.2.14 versus bash 3.2 on Mac OS X 10.11 for instance show:

$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
$ bash code
38643
38643
$ ksh code
38650
$ 

Also relevant here is that yes performs no signal handling so inherits whatever there is to be inherited from the parent shell process; if by contrast we do perform signal handling—

$ cat sighandlingcode 
perl -e '$SIG{INT} = sub { die "ouch\n" }; sleep 5' &
pid=$!
sleep 2
kill -INT $pid
$ bash sighandlingcode 
ouch
$ ksh sighandlingcode 
ouch
$ 

—the SIGINT is triggered regardless the parent shell, as perl here unlike yes has changed the signal handling. There are system calls relevant to signal handling which can be observed with things like DTrace or here strace on Linux:

-bash-4.2$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ rm foo*; strace -o foo -ff bash code
21899
21899
code: line 9: 21899 Terminated              yes > /dev/null
-bash-4.2$ 

We find that the yes process ends up with SIGINT ignored:

-bash-4.2$ egrep 'exec.*yes' foo.21*
foo.21898:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
foo.21899:execve("/usr/bin/yes", ["yes"], [/* 24 vars */]) = 0
foo.21903:execve("/usr/bin/pgrep", ["pgrep", "yes"], [/* 24 vars */]) = 0
foo.21904:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
-bash-4.2$ grep INT foo.21899
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=21897, si_uid=1000} ---
-bash-4.2$ 

Repeat this test with the perl code and one should see that SIGINT is not ignored, or also that under pdksh there is no ignore being set as there is in bash. With "monitor mode" turned on like it is in interactive mode in bash, yes is killed.

-bash-4.2$ cat monitorcode 
#!/bin/bash
set -m
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ ./monitorcode 
22117
[1]+  Interrupt               yes > /dev/null
-bash-4.2$ 
  • I tested this with bash,dash,ksh,posh,pdksh,sh,yash, and zsh, and all except posh ignore SIGINT and SIGQUIT in their backgrounded processes in noninteractive mode. I wonder what the historical reason for this is (tried asking here stackoverflow.com/questions/45106725/…). – PSkocik Jul 14 '17 at 17:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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