I think the problem is that you're expecting "$LINENO"
to give you the line of execution for the last command, which might almost work, but clean_a()
also gets its own $LINENO
and that you should do instead:
error "something!
line: $1
...
But even that probably wouldn't work because I expect it will just print the line on which you set the trap
.
Here's a little demo:
PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD
trap 'fn "$LINENO"' EXIT
fn() { printf %s\\n "$LINENO" "$1"; }
echo "$LINENO"
CMD
OUTPUT
DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1
So the trap
gets set, then, fn()
is defined, then echo
is executed. When the shell completes executing its input, the EXIT
trap is run and fn
is called. It is passed one argument - which is the trap
line's $LINENO
. fn
prints first its own $LINENO
then its first argument.
I can think of one way you might get the behavior you expect, but it kinda screws up the shell's stderr
:
PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
trap 'fn "$LINENO" "$LASTNO"' EXIT
fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
echo "$LINENO"
CMD
OUTPUT
DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3
It uses the shell's $PS4
debug prompt to define $LASTNO
on every line executed. It's a current shell variable which you can access anywhere within the script. That means that no matter what line is currently being accessed, you can reference the most recent line of the script run in $LASTNO
. Of course, as you can see, it comes with debug output. You can push that to 2>/dev/null
for the majority of the script's execution maybe, and then just 2>&1
in clean_a()
or something.
The reason you get 1
in $LASTNO
is because that is the last value to which $LASTNO
was set because that was the last $LINENO
value. You've got your trap
in the archieve_it()
function and so it gets its own $LINENO
as is noted in the spec below. Though it doesn't appear that bash
does the right thing there anyway, so it may also be because the trap
has to re-exec the shell on INT
signal and $LINENO
is therefore reset. I'm a little fuzzy on that in this case - as is bash
, apparently.
You don't want to evaluate $LASTNO
in clean_a()
, I think. Better would be to evaluate it in the trap
and pass the value trap
receives in $LASTNO
through to clean_a()
as an argument. Maybe like this:
#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
SIGHUP SIGINT SIGTERM SIGQUIT
while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1
Try that - it should do what you want, I think. Oh - and note that in PS4=^M
the ^M
is a literal return - like CTRL+V ENTER.
From the POSIX shell spec:
Set by the shell to a decimal number representing the current sequential line number (numbered starting with 1) within a script or function before it executes each command. If the user unsets or resets LINENO
, the variable may lose its special meaning for the life of the shell. If the shell is not currently executing a script or function, the value of LINENO
is unspecified. This volume of IEEE Std 1003.1-2001 specifies the effects of the variable only for systems supporting the User Portability Utilities option.
There are several ways you can cut off the effect of Ctrl+C:
- Change the terminal setting so that it doesn't generate a signal.
- Block the signal so that it is saved for later delivery, when the signal becomes unblocked.
- Ignore the signal, or set a handler for it.
- Run subprocesses in a background process group.
Since you want to detect that Ctrl+C has been pressed, ignoring the signal is out. You could change the terminal settings, but then you would need to write custom key processing code. Shells don't provide access to signal blocking.
You can however isolate subprocesses from receiving the signal automatically by running them in a separate process group. Interactive shells run background commands in a separate process group by default, but non-interactive shells run them in the same process group, and all processes in the foreground process group receive a signal from terminal events. To tell the shell to run background jobs in a separate process group, run set -m
. Running setsid ping …
is another way of forcing ping
to run in a separate process group.
set -m
interrupted=
trap 'echo Interrupted, but ping may still be running' INT
set -m
ping … &
while wait; [ $? -ge 128 ]; do echo "Waiting for background jobs"; done
echo ping has finished
If you want Ctrl+Z to suspend a background process group, you'll need to propagate the signal from the shell.
Controlling signals finely is a bit of a stretch for a shell script, and shells other than ATT ksh tend to be a little buggy when you reach the corner cases, so consider a language that gives you more control such as Perl, Python or Ruby.
Best Answer
trap '' INT
is meant to ignore SIGINTs for the shell and all its children.But looking at
strace
outputs onscp
, it looks likescp
installs its own SIGINT handler which cancels the SIG_IGN above.The only way to prevent it from getting the SIGINT would be to run it in a different process group like with:
or
or tell the tty driver to stop sending SIGINT upon Ctrl-C (like with
stty -isig
, orstty intr ''
for^C
alone), though you'd want to restore the settings afterwards: