Identifying Received Signal Name in Bash

Identifying received signal name in Bash

(If you only have the number of a signal and want the name, kill -l $SIGNAL_NUM prints the name of a signal; you can avoid that by using the signal names instead of numbers in your call to trap as below.)

This answer says that there's no way to access the signal name, but if you have a separate function for each signal that you trap, then you already know the signal name:

trap 'echo trapped the HUP signal' HUP
trap 'echo different trap for the INT signal' INT

In many cases, that may be sufficient, but another answer on that same question uses that fact to provide a workaround to fake the behavior you want. It takes a function and a list of signals and sets a separate trap for each signal on that function called with the signal name, so internally it's actually a separate function for each signal but it looks like a single trap on a single function that gets the signal name as an argument:

Code:

#!/bin/bash

trap_with_arg() {
func="$1" ; shift
for sig ; do
trap "$func $sig" "$sig"
done
}

func_trap() {
echo "Trapped: $1"
}

trap_with_arg func_trap INT TERM EXIT

echo "Send signals to PID $$ and type [enter] when done."
read # Wait so the script doesn't exit.

If I run that, then I can send signals to the process and I get output like

Trapped: INT
Trapped: TERM
Trapped: EXIT

Is it possible to detect *which* trap signal in bash?

No documentation hints of any argument or variable holding the signal that was trapped, so you'll have to write a function/trap statement for each trap you want to behave differently.

Find signal number by signal name

Try constraining the match with a word boundary:

trap -l | sed -nr 's/.*\b([0-9]+)\) SIGTSTP.*/\1/p'

This tested correctly [for me] for the target string as well as SIGPIPE.

Identify whether a process was killed by a signal in bash

wait is a syscall and also a bash builtin.

To differentiate the two cases from bash run the program in the background and use the builtin wait to report the outcome.

Following are examples of both a non-zero exit code and an uncaught signal. These examples use the exit and kill bash builtins in a child bash shell, instead of a child bash shell you would run your program.

$ bash -c 'kill -s SIGTERM $$' & wait
[1] 36068
[1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
$ bash -c 'exit 143' & wait
[1] 36079
[1]+ Exit 143 bash -c 'exit 143'
$

As to why you see Terminated printed to the terminal even when you redirect stdout and stderr the reason is that is printed by bash, not by the program.

Update:

By explicitly using the wait builtin you can now redirect its stderr (with the exit status of the program) to a separate file.

The following examples show the three types of termination: normal exit 0, non-zero exit, and uncaught signal. The results reported by wait are stored in files tagged with the PID of the corresponding program.

$ bash -c 'exit 0' & wait 2> exit_status_pid_$!
[1] 40279
$ bash -c 'exit 143' & wait 2> exit_status_pid_$!
[1] 40291
$ bash -c 'kill -s SIGTERM $$' & wait 2> exit_status_pid_$!
[1] 40303
$ for f in exit_status_pid*; do echo $f: $(cat $f); done
exit_status_pid_40279: [1]+ Done bash -c 'exit 0'
exit_status_pid_40291: [1]+ Exit 143 bash -c 'exit 143'
exit_status_pid_40303: [1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
$

In bash script, what happens when a signal is trapped

$ cat foo.sh
_sigint()
{
echo "received SIGINT"
}

trap _sigint INT

sleep 10000
echo "status=$?"
$ bash foo.sh # <-- start foo.sh and then press CTRL-C
^Creceived SIGINT
status=130

Some explanation:

  • sleep runs as a child process of bash. bash and sleep are running in the same process group which is now the foreground process group.

  • When CTRT-C is pressed, it'll generate SIGINT and the signal will be sent to all processes in the foreground process group so both bash and sleep will receive SIGINT.

  • According to bash manual (can also see man bash)

    If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.

    So here bash would wait for sleep to be killed (The default SIGINT behavior is to terminate the process. See signal(7)) first and then bash runs its SIGINT trap.

  • According to bash manaul

    The return value of a simple command is its exit status, or 128+n if the command is terminated by signal n.

    The SIGINT is 2 (see kill -l) so sleep's exit status is 128+2=130.

How can I tell in Linux which process sent my process a signal

Two Linux-specific methods are SA_SIGINFO and signalfd(), which allows programs to receive very detailed information about signals sent, including the sender's PID.

  • Call sigaction() and pass to it a struct sigaction which has the desired signal handler in sa_sigaction and the SA_SIGINFO flag in sa_flags set. With this flag, your signal handler will receive three arguments, one of which is a siginfo_t structure containing the sender's PID and UID.

  • Call signalfd() and read signalfd_siginfo structures from it (usually in some kind of a select/poll loop). The contents will be similar to siginfo_t.

Which one to use depends on how your application is written; they probably won't work well outside plain C, and I wouldn't have any hope of getting them work in Java. They are also unportable outside Linux. They also likely are the Very Wrong Way of doing what you are trying to achieve.

Bash script catch signal but wait afterwards for processes to terminate

Something rewritted

In order to avoid some useless forks.

clock(){  local prefix=C interval=2
trap : RTMIN{,+{{,1}{1,2,3,4,5},6,7,8,9,10}}
while :;do
printf "%s: %(%d.%m %H:%M:%S)T\n" $prefix -1
sleep $interval
done
}

volume(){ local prefix=V vol=() field playback val foo
while IFS=':[]' read field playback val foo;do
[ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] &&
vol+=(${val%\%})
done < <(amixer get Master)
suffix='%%'
if [ "$vol" = "off" ] ;then
icon="&" #alternative: deaf:  mute: 
suffix=''
elif (( vol > 50 )) ;then icon="("
elif (( vol > 30 )) ;then icon="("
else icon="'"
fi
printf -v values "%3s$suffix " ${vol[@]}
printf "%s%s %s\n" $prefix "$icon" "$values"
}

clock & volume &

trap volume RTMIN+2
trap : RTMIN{,+{{,1}{1,3,4,5},6,7,8,9,10,12}}
echo -e "To get status, run:\n kill -RTMIN+2 $$"

while :;do wait ;done

Regarding my last comment about stereo bug, there is a volume function working for stereo, mono or even quadra:

volume(){
local prefix=V vol=() field playback val foo
local -i overallvol=0
while IFS=':[]' read field playback val foo ;do
[ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] && {
vol+=($val)
val=${val%\%}
overallvol+=${val//off/0}
}
done < <(
amixer get Master
)
overallvol=$overallvol/${#vol[@]}
if (( overallvol == 0 )) ;then
icon="&"
elif (( overallvol > 50 )) ;then
icon="("
elif (( overallvol > 30 )) ;then
icon="("
else
icon="'"
fi
printf "%s%s %s\n" $prefix "$icon" "${vol[*]}"
}

or even:

volume(){
local prefix=V vol=() field playback val foo icons=(⏻ ¼ ¼ ¼ ½ ½ ¾ ¾ ¾ ¾ ¾)
local -i overallvol=0
while IFS=':[]' read field playback val foo ;do
[ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] && {
vol+=($val)
val=${val%\%}
overallvol+=${val//off/0}
}
done < <(
amixer get Master
)
overallvol=$overallvol/${#vol[@]}
printf "%s%s %s\n" $prefix "${icons[(9+overall)/10]}" "${vol[*]}"

Some explanations

Regarding useless forks in volume() function

I've posted there some ideas to improve the job, reducing resource eating and doing same job of choosing an icon as function of current volume set.

About while :;do wait;done loop

As requested sample stand for an infinite loop in backgrounded sub function, the main script use same infinite loop.

But as question title stand for wait afterwards for processes to terminate, I have to agree with oguz-ismail's comment.

In fact, last line would better be written:

until wait;do :;done

For more information on how wait command work and good practice, please have a look on good oguz-ismail's answer

What is the bash command on Ubuntu to list all the signals?

You can use the trap builtin:

trap -l

From help trap:

-l print a list of signal names and their corresponding numbers



Related Topics



Leave a reply



Submit