Controlling shell processes using signals

Kirsty Pargeter, 123RF

Kirsty Pargeter, 123RF

Clear Signals

Instead of just killing processes, professionals cleverly catch their communication and steer the computer's tasks in the right direction using signals.

In most cases, kill is used as a last resort. It ends a process without mercy. Systems administrators sometimes kill a process with the kill -9 <PID> command – using the signal 9, which means kill even if the signal is not catchable or ignorable) and the process ID (PID) argument. You can get the PID by using the ps command.

Signals

The standard Linux signals are listed in Table 1. Related systems, such as FreeBSD, have their own variations, with the signals 1, 3, 9, and 15 being the only uniform ones throughout.

Table 1

Signals

1 SIGHUP Separates child from parent process Ends process
2 SIGINT Ends process Same as Ctrl+C
3 SIGQUIT Ends process Process can create core dump
4 SIGILL Ends process After a false call (no privileges, unknown functions)
5 SIGTRAP Ends process Process triggers trace/debugger
6 SIGABRT Ends process From process itself
7 SIGBUS Ends process System call after memory access errors
8 SIGFPE Ends process Division by 0
9 SIGKILL Ends process No data written, risk of data loss
10 SIGUSR1 User-defined signal
11 SIGSEGV Ends process After memory access errors
12 SIGUSR2 User-defined signal
13 SIGPIPE Ends process Pipe error
14 SIGALRM Ends process After timer expiration
15 SIGTERM Ends process Restores files
16 SIGSTKFLT Ends process After co-processor stack error
17 SIGCHLD Ends child process Triggered by parent
18 SIGCONT Continues process
19 SIGSTOP Pauses process
20 SIGTSTP Pauses process Same as Ctrl+Z

You can determine which of the two methods your system uses with kill -l . You can use the kill command in the format kill -<number> <PID> or kill -s <signal_name> <PID> . When using the signal name, you don't need the preceding SIG , so you can either do:

$ kill -KILL 29737

or:

$ kill -9 29737

and it will do exactly the same thing: kill the process instantly.

For some of the examples, the target for the signals will be daemon.sh that I created in Listing 1. The program shows its shell command, its own PID, and the parent process PID in a loop function.

Listing 1

daemon.sh

#! /bin/sh
while true;
do
  echo $0 $$ $PPID
  sleep 1
done

Gently Removed

The basic process-ending method is to use SIGHUP , where the system proceeds as if you had quit the terminal. Conversely, you can prevent this by using nohup at program startup. The application continues to run after you log off, such as with an SSH session.

As long as no one affects the process or shuts down the system, the script runs until it terminates by itself. You can read the output in the nohup.out protocol file by using tail -f nohup.out .

You can restart most system daemons in the same way: kill -HUP <daemon_PID> . It's still recommended, however, to use the daemon's init script or the system's start/stop mechanism instead.

If necessary, you can halt processes to allow other necessary resources to run. For example, the daemon.sh script gets a signal 19 while going through its motions, which pauses the process. You can continue the process using kill -18 <PID> (Figure 1).

Figure 1: Starting, pausing, and continuing a process.

Intercepting Signals

There is really no way of capturing, blocking, or ignoring the SIGKILL and SIGSTOP signal with a trap. However, you can use all the other signals in shell scripts to elicit another response. You can do this using the trap '<response>' <signals> command in a shell script.

The trap '' 2 line ignores the signal. Without an option or a response, the command undefines almost all signals at the end of the shell script. Signal 10 essentially halts the script in Listing 2 with the output [2]+ User-defined signal 1 ./message.sh . The trap -l command lists the usable signals.

Listing 2

ignoresignals.sh

#! /bin/sh
trap
while true; do
  clear
  echo $0 $$
  sleep 1
done

A trap opens up many possibilities. It allows some scripts to delete temporary files when unintentionally logging off the shell. You can also prevent stopping a script through pilot error by having it ignore signals. Conversely, you can use this technique to enter into a running script without needing to restart it.

Listing 3 (reaction.sh ) shows you how you can do this using a function. In the example, the script defines a variable and then displays it. Figure 2 shows the result.

Listing 3

reaction.sh

#! /bin/sh
a="Default"
# Define function
signaltrap() {
  echo -n "Enter new value: ";read a
  if [ -z "$a" ];
    then
      echo "No entry -> Abort!"
      exit
  fi
  continue
  exit 0
}
# Trap signal 2 ([Crl]+[C])
trap 'signaltrap' 2
while true;
do
  echo "$$: $a"
  sleep 3
done
Figure 2: Using a signal, you can control the result of a script by defining a new variable value, as in this example.

Because of the continue after the loop, the script continues with the given variable value. Another shell sends the kill -2 signal. The terminal with the application accepts the input and the script continues. Another Ctrl+C and an empty input terminate the program.

A Clock for the Shell

You can find a program much prettier than the first attempt at a self-written script. For example, take a look at a retro clock, complete with sound output in the style of a C64 classic home computer (Figure 3). To make it work, you need two programs: figlet and beep .

Figure 3: With the help of a couple of simple tools, you can program a clock in the shell in the style of a C64.

Once you've installed the tools, the scripts in Listing 4 (clockmenu.sh ) and Listing 5 (clock.sh ) let you experiment further. To begin, start clock.sh , which creates behind a file named .clock.pid that contains the PID. Then, call clockmenu.sh , which extracts the number of the other process from the file. Now you can control the clock.

Listing 4

clockmenu.sh

#! /bin/sh
# Get clock time process ID
clockpid=$(cat .clock.pid)
while true;
do
  clear
  echo "Control of shell clock"
  echo " "
  echo "(l) Local time"
  echo "(u) UTC"
  echo "(w) Set alarm"
  echo "(e) Stop alarm"
  echo "(E) End control"
  echo " "
  echo -n "Select function: ";read f
  if [ "$f" = "E" ]; then
    exit
  elif [ "$f" = "e" ]; then
    kill -15 $clockpid
  elif [ "$f" = "w" ]; then
    echo -n "Enter wake-up time (null for delete): ";read wt
    echo "$wt" > .clock.wt
    kill -1 $clockpid
  elif [ "$f" = "l" ]; then
      kill -10 $clockpid
  elif [ "$f" = "u" ]; then
    kill -12 $clockpid
  fi
done

Listing 5

clock.sh

#! /bin/bash
# Ignore signals 2 and 20
trap '' 2 20
# Print process ID for controlling "clockmenu.sh"
echo $$ > .clock.pid
# Define empty variable for wake-up time $wt
wt=""
# Set time zone: local time
lt="l"
# Get wake-up time, or null
if [ -e .clock.wt ]; then
  wt=$(cat .clock.wt)
else
  touch .clock.wt
fi
# Main loop
while true;
do
  # Assign action to signals -- important:
  # for loop continuation,
  # don't forget continue!
  trap 'wt=$(cat .clock.wt); continue' 1
  trap 'lt="l";            continue' 10
  trap 'lt="u";            continue' 12
  # Get times
  time=$(date +%H:%M:%S)
  utc=$(date -u +%H:%M:%S)
  shorttime=$(date +%H:%M)
  date=$(date +%A\ %d.%m.%Y)
  week=$(date +%V)
  hourchime=$(date +%I)
  quarterhourchime=$(date +%M)
  # Progess bar seconds:
  # For loop can't handle "08" or "09".
  # Therefore, seconds first stored in $a and for
  # $seconds and cast to computer bc for 0.
  a=$(date +%S)
  seconds=$(echo $a -0 | bc)
  clear
  # Display time
  if [ "$lt" = "l" ]; then
    figlet -f banner $time
    tzone="Local time"
  elif [ "$lt" = "u" ]; then
    figlet -h banner $utc
    tzone="UTC"
  fi
  # Display additional data
  echo " "
  echo "Set wake-up time: $wt"
  echo "$date : $week. Calendar week  Timezone: $tzone"
  echo "----------------------------------------------------------------"
  # Second hand
  for((i=0; i<$seconds; i++)); do
    echo -n "#"
  done
  # Chime
  if [ "$quarterhourchime" != "$alarm" ]; then
    if [ $quarterhourchime -eq 15 ]; then
      beep -f 800
    elif [ $quarterhourchime -eq 30 ]; then
      beep -f 800 -r 2 -l 500 -d 500
    elif [ $quarterhourchime -eq 45 ]; then
      beep -f 800 -r 3 -l 500 -d 500
    elif [ $quarterhourchime -eq 0 ]; then
      beep -f 800 -r 4 -l 500 -d 500
      beep -f 700 -r $hourchime -l 500 -d 600
    fi
    alarm=$quarterhourchime
  fi
  # Alarm
  if [ "$shorttime" = "$wt" ]; then
    if [ "$wt" != "$wakeupalarm" ]; then
      for (( i=1; i <= 5; i++ )); do
        beep -f 1000 -n -f 2000 -n -f 1500 -n -f 500 -n -f 300 -n -f 3000
      done
      wakeupalarm=$wt
    fi
  fi
  sleep 1
done
# Cleanup
rm .clock.pid

Figure 4 shows the clock menu that, although rather Spartan, performs all the required functions. Setting the alarm time is done by entering it in the SS:MM format.

Figure 4: You can control the clock for the shell using a simple menu.

Thanks to the figlet tool, the clock appears in a large display. If you set an alarm, the script displays it. The alarm sound will turn off by itself, and the sound is unmistakable. With beep , you can conjure up different sounds from the computer.

The same is true for the hour chime. As with a classic clock, the quarter hour chimes are in a higher tone and the hour chimes are in a lower one. The hour chimes are divided into two 12-hour periods, and you can enter world time (UTC) or local time. Entering e in clockmenu.sh stops the clock.

Note that stopping and restarting the controlling software (i.e., clockmenu.sh ) is necessary after restarting the clock or the signals get sent to the wrong or non-existent process. The sounds that beep creates are generally emitted by the PC's speaker.

You can also use Htop to control the clock, as well as other processes shown in this article. In Figure 5, you can see how Htop is used to kill the reaction.sh script.

Figure 5: The Htop console tool also works with signals, allowing you to control processes.

Conclusion

A Linux system provides a wealth of possibilities through its construct of processes. If you get more involved in the subject, you will soon discover that the robustness and long runtimes of Linux/UNIX computers largely rest upon this sophisticated method that makes perpetual restarts unnecessary.

For ambitious programmers, there are also fascinating opportunities to enhance a shell script with a user interface for communicating with the running program.