Shell Script Get Ctrl+Z with Trap

How to trap CTRL+Z in Linux POSIX shell script; possible or not?

To ignore Ctrl+Z typed from a terminal, you can do:

trap '' TSTP

# or possibly equivalently
trap '' 18
trap '' 20
trap '' 24

As noted by others, there are other signals that cannot be blocked, such as SIGKILL and SIGSTOP.

Many shells map Ctrl+\ to SIGKILL and Ctrl+C to SIGINT.

stty -a should show settings for sending "susp" (SIGTSTP), "intr" (SIGINT) and "quit" (SIGKILL). These keystrokes can be rebound by the user if desired.

Editor's notes:

  • You cannot use SIGTSTP pseudonym in a POSIX shell script.

  • Even if you know for sure what exact signal number your terminal sends, if writing a script for general use, you ought to block all those three possibilities.

  • ShellCheck warning, and I quote:

    Trapping signals by number is not well defined. Prefer signal names. [SC2172]

    Means these signal numbers can vary between platforms, try 15 for SIGTERM and you will see no warning at all as that one is POSIX standardized. Ergo, you might want to disable these after you've tested your code with:

    # shellcheck disable=SC2172

    Link to wiki: https://github.com/koalaman/shellcheck/wiki/SC2172

How to trap input to control the screenshot function?

As an alternative to using readline on a shell prompt, consider the following bash solution. Solution closely matching requirements. Basically:

Loop Forever:

  • Wait for Ctrl/A
  • Start screenshot in backgroun
  • Wait for Ctrl/B
  • Stop screen shot, Run external screenshot.sh

It uses bash to read one-character at a time line delimiter is needed.

Side note: Given program running in a window with dedicated terminal, the Ctrl/A and Ctrl/B can be replaced with something easier - just 'A' and 'B'.

#! /bin/bash

screenshot(){
rm -f /tmp/out.mp4
exec ffmpeg -video_size 1920x1080 -framerate 25 -f x11grab -i :0.0+0,0 /tmp/out.mp4
}

while true ; do
a=""
echo "Type CTRL/A to start"
while [ "$a" != $'\x01' ] ; do
read -N1 a
done
screenshot &
sleep 3
echo
echo "Type CTRL/B to start"
while [ "$a" != $'\x02' ] ; do
read -N1 a
done
echo "Processing ..."
kill $!
bash myscreenshot.sh &
done

how can the bash script kill self if it received ctrl z

Just force an exit when the Python call fails:

python xxx.py || exit 1

You could use break instead of exit to just leave the loop. More advanced error handling can be achieved by evaluating $?; here's an example how to store the return value and reuse it:

python xxx.py
result=$?
if [ ${result} -ne 0 ]; then
# Error
echo "xxx.py exited with ${result}"
...
else
# Success
...
fi

As a general rule, not only regarding Bash scripting but programming in general, always check return codes of commands for errors and handle them. It makes debugging and your working life in general easier in the long run.

Trap signals both in script and subshells

There is no easy way to propagate the particular signal from child to parent.
However, exit code is 130 if the program is terminated by Ctrl-C in many cases. fzf follows it also.
Checking exit status and sending signal to own process is one of the workaround.

#!/bin/sh
trap 'echo "exiting.."; exit 0;' INT

var1=$(echo "ab\nab" | fzf)
[ $? -eq 130 ] && kill -INT $$
var2=$(echo "cd\ncd" | fzf)
[ $? -eq 130 ] && kill -INT $$

However, it is not common way.
If it needs to do something when terminated with any errors, using set -e and trapping EXIT or other signals is general way.

#!/bin/sh
set -e
trap 'echo "exiting.."; exit 0;' EXIT

var1=$(echo "ab\nab" | fzf)
var2=$(echo "cd\ncd" | fzf)

bash trap will echo from keyboard Ctrl-C while not kill 2

With kill -s SIGINT 50138, you are only sending the signal to the shell's process, and that has to wait for sleep 1000 to finish, because sleep doesn't receive the signal.

Control-C, though, causes the terminal to send SIGINT to every process in the current process group, so both your shell script and sleep receive it. Your script still doesn't process the trap command until sleep completes, but sleep exits immediately in response to the SIGINT it just received from the terminal.

If your kill supports it, you can also use kill -s SIGINT -50138 (note the negative process id) to send SIGINT to the entire process group.

Can successfully trap CTRL-Z, but not SIGTSTP

First you must not trap SIGTSTP for the shell itself (it should ignore it), only for its child. Second if you really want to write a job controller shell, you need to manage children with the help of process group and correctly set the foreground group. Writing a shell that behave correctly with job control is a heavy task. Read POSIX standard about shells, groups, sessions, terminal control.

About your current problem. If your sub process does an exec then each handled signal is reset to its default behavior. This is because exec recovers the old code with the new one, so the previously set handler is no more available. Now you must let the child behave normally against TSTP and just let the parent track its status either with a synchronous call to wait/waitpid or with the asynchronous help of SIGCHLD. When the child stops or terminates, the parent is able to see it in the returned status (WIFEXITED, WIFSIGNALED, WIFSTOPPED).



Related Topics



Leave a reply



Submit