Reputation: 135
sorry I cannot figure out what is wrong with the following. When I hit ctrl-c in a script the trap works, but does not when the script is source executed.
Example (test2.sh):
#!/bin/bash
ctrl_c()
{
echo "user canceled."
}
trap ctrl_c INT
sleep 500
trap - INT
The following happens:
[root@localhost ~]# ./test2
^Cuser canceled.
[root@localhost ~]# . test2
^C
This is RHEL 7, Bash 4.2, but the same problem is also happening in other Linux distributions and also Bash 4.4.
Please help. Thanks!
Upvotes: 3
Views: 1623
Reputation: 135
So here is how I solved my problem. Maybe it's not the best or most elegant way but it seems to work just fine.
The trick is to run the procedure in a background task (child process), then wait for the process to finish. While the background process ($!) is running, trap INT will work and do the cleanup. Since I need to source some commands I write them into a file and then source execute the files in the main script. These file, however, are cleaned upon Ctrl-C and hence the script ends.
For example:
# scan.sh
scan.sh, not sourced. Aborting...
# source scan.sh
[|] Working...^C
scan.sh: waiting for process 22390 to terminate...
scan.sh: user canceled.
[1]+ Interrupt doit $f_menu $f_alias
I did not find a working solution to eliminate the "Interrupted" message, but that's not really a concern.
#!/bin/bash
....
# Preliminary options and environment.
save_monitor=$(set +o | grep -w monitor)
set +o monitor # Turn off monitor mode.
save_history=$(set +o | grep -w history)
set +o history # Turn off command line history.
save_extglob=$(shopt -p extglob)
shopt -s extglob
f_menu=/tmp/1_$$
f_alias=/tmp/2_$$
iam=scan.sh
cleanup()
{ # Requires $1 (f_menu) $2 (f_alias) args.
# Restore shell the previous shell environment.
unset i f_pid f_menu f_alias aver iam list aname line sourced
unset spinner doit cleanup ctrl_c
trap - INT
\rm -f $1 $2 # Don't use rm alias.
eval "$save_extglob"
eval "$save_monitor"
eval "$save_history"
unset save_extglob save_history save_monitor
}
# Exit if the script was not sourced.
$(return >/dev/null 2>&1) && sourced=1 || unset sourced
[[ "$sourced" ]] || { echo; echo "$iam, not sourced. Aborting..."; exit 1; }
ctrl_c()
{ # Requried: f_pid f_quit
# Optional: f_menu $f_alias
kill -TERM $f_pid
while (( f_pid )); do
echo; echo "$iam: waiting for process $f_pid to terminate..."
sleep 2
[[ $(kill -0 $f_pid 2>&-) ]] || f_pid=
done
echo "$iam: user canceled."
cleanup $f_menu $f_alias
}
trap ctrl_c INT
spinner()
{
# Optional: $1
local disp
disp='|/-\'
while kill -0 $! 2>&-; do
printf " [%c] %s..." "$disp" "$1"
disp=${disp#?}${disp%%???}
sleep .1
printf "\r"
done
}
doit()
{
# Requires $1 (f_menu) $2 (f_alias) args.
.....
}
echo
doit $f_menu $f_alias &
f_pid=$!; spinner "Working"; wait $f_pid
# Following will no longer work after user hit control-c.
source $f_alias 2>&-
cleanup $f_menu $f_alias 2>&-
# end
Upvotes: 0
Reputation: 881323
I seem to recall that interactive shells (which is where you're doing the trap because you're sourcing it rather than running it asynchronously) have special handling for SIGINT
. They capture it themselves so that process control calls can be interrupted.
I suspect that's getting in the way of what you're trying to do.
UPDATE: Just had a look at the bash
man-page, the SIGNALS
section there is a little vague but it does seem to confirm my recollection re the special handling in interactive shells.
SIGNALS
When
bash
is interactive, in the absence of any traps, it ignoresSIGTERM
(so thatkill 0
does not kill an interactive shell), andSIGINT
is caught and handled (so that thewait
builtin is interruptible). In all cases,bash
ignoresSIGQUIT
.
Upvotes: 2