Right way to write bash script to launch background processes
Mar 4, 2021
4 minute read

Table of Contents

  1. Interesting problem occurred
  2. Behind the scenes
    1. Job Control
    2. Bash send SIGHUP on exit
  3. Conclusion

Interesting problem occurred

Recently I use a bash script to launch some processes running in the background. I handle the process termination improperly due to being not familiar with Unix and with Bash. I do something like this.

I write a helper script to launch a background process, which is the “sleep” command in this case.

#!/bin/bash
sleep 1000 &
PID=$!
echo $PID launched
echo $PID >pid

And then I want to use kill -2 $PID to send the SIGINT 1 to stop this process. But it does not work at all.

Behind the scenes

After checking the process status file of this process, I find out that it ignores the SIGINT 1 signal.

cat /proc/`cat pid`/status | grep Sig

The value of the "SigIgn" field of the process status file is 6, which is 110 in bit representation. This value means the SIGINT and SIGQUIT signals 1 being ignored. 2

SigQ:   0/3793
SigPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000006
SigCgt: 0000000000000000

Run below useful script to see whole procedures.

#!/bin/bash

sleep 1000 &
PID=$!

ps $PID >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
    echo Process is alive
fi

cat /proc/$PID/status | grep SigIgn

kill -2 $PID
ps $PID >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
    echo Process is still alive

    kill $PID

    ps $PID >/dev/null 2>&1
    if [[ $? -eq 1 ]]; then
        echo Process is killed
    fi
fi

If we execute the above script ./demo_with_kill.sh in interactive mode, bash -i ./demo_with_kill.sh 3. We will see the different result outcomes. We can see the sleep process’s "SigIgn" value being 0. This means it can receive SIGINT 1 now.

Process is alive
SigIgn: 0000000000000000

What is the difference between interactive mode and non-interactive mode of bash? The monitor mode, aka Job Control 4, is disabled by default in non-interactive mode.

-m

Monitor mode. Job control is enabled. This option is on by default for interactive shells on systems that support it (see JOB CONTROL above). Background processes run in a separate process group and a line containing their exit status is printed upon their completion. — is quoted from “man 1 bash

Job Control 4

Job control refers to the protocol for allowing a user to move between multiple process groups (or jobs) within a single login session. Bash runs without it will make background child process ignoring SIGINT and SIGQUIT signals 1.

I believe SIGINT and SIGQUIT signals 1 are set ignored by below fragment codes come from bash/execute_cmd.c#L5394-L541.

void
setup_async_signals ()
{
#if defined (__BEOS__)
  set_signal_handler (SIGHUP, SIG_IGN);	/* they want csh-like behavior */
#endif

#if defined (JOB_CONTROL)
  if (job_control == 0)
#endif
    {
      /* Make sure we get the original signal dispositions now so we don't
         confuse the trap builtin later if the subshell tries to use it to
         reset SIGINT/SIGQUIT.  Don't call set_signal_ignored; that sets
         the value of original_signals to SIG_IGN. Posix interpretation 751. */
      get_original_signal (SIGINT);
      set_signal_handler (SIGINT, SIG_IGN);

      get_original_signal (SIGQUIT);
      set_signal_handler (SIGQUIT, SIG_IGN);
    }
}

Bash send SIGHUP on exit

Bash will send all child processes SIGHUP signal 1 on exit if it being enabled huponexit 5 option.

  • Execute cmd shopt -s huponexit to enable it.
  • Execute cmd shopt -u huponexit to disable it. 5

It is looked like being disabled by default in my case, not acting like the Orphaned Process Groups | The GNU C Library Reference Manual saying.

Conclusion

So the better way to write a background job script is

  1. shopt -u huponexit 5 disable huponexit 5 shell option to avoid bash sending background processes SIGHUP signal on exit.
  2. set -m 3 enable job control to avoid background processes ignoring SIGQUIT and SIGINT signals.

    #!/bin/bash
    shopt -u huponexit
    set -m

Footnotes

1 24.2.2 Termination Signals | The GNU C Library Reference Manual

2 How to read bitmask for the signals | Red Hat Customer Portal

3 The Set Builtin | bash manual

4 References

5 The Shopt Builtin | bash manual




comments powered by Disqus