If I use trap like described e.g. on http://linuxcommand.org/wss0160.php#trap to catch ctrl-c (or similar) and cleanup before exiting then I am changing the exit code returned.

Now this probably won't make difference in the real world (e.g. because the exit codes are not portable and on top of that not always unambiguous as discussed in Default exit code when process is terminated?) but still I am wondering whether there there is really no way to prevent that and return the default error code for interrupted scripts instead?

Example (in bash, but my question shouldn't be considered bash-specific):

trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _


$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
$ echo $?

(Edited to remove to make it more POSIX-conform.)

(Edited again to make it a bash script instead, my question is not shell-specific though.)

Edited to use the portable "INT" for trap in favor of the non-portable "SIGINT".

Edited to remove useless curly braces and add potential solution.


I solved it now by simply exiting with some error codes hardcoded and trapping EXIT. This might be problematic on certain systems because the error code might differ or the EXIT trap not possible but in my case it's OK enough.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
  • Your script looks a bit strange: you tell read to read from the current coprocess and trap cmd SIGINT will not work as the standard says that you should use trap cmd INT. – schily Oct 12 '15 at 12:36
  • Ah yes, under POSIX it's of course without the SIG-prefix. – phk Oct 12 '15 at 12:40
  • Oops, but then "read -p" wouldn't be supported either, so I am going to adapt it for bash. – phk Oct 12 '15 at 12:43
  • @schily: I don't know what you mean with "coprocess" though. – phk Oct 12 '15 at 13:09
  • Well, the Korn Shell man page says read -p reads input from the current coprocess. – schily Oct 12 '15 at 14:15

All you need to do is change the EXIT handler inside your cleanup handler. Here's an example:

cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '
  • did you mean trap cleanup INT instead of trap cleanup EXIT? – Jeff Schaller Oct 12 '15 at 12:45
  • I think I meant EXIT. When exit is finally called, however that's done, we change the trap to exit with return 0. I don't believe signal handlers are recursive. – rocky Oct 12 '15 at 12:59
  • OK, in your example I am not trapping (SIG)INT at all which is actually what I want in order to do some cleaning up even if the user is exiting my interactive script through ctrl-c. – phk Oct 12 '15 at 13:06
  • @phk Ok. I think though you get the idea of how to force exit to return 0 or some other value which what I gathered was the problem. I assume you'll be able to adjust the code to put the trap EXIT setting inside your real cleanup handler which is called by SIGINT. – rocky Oct 12 '15 at 13:12
  • @rocky: My goal was to have a trap handler while not changing the exit code that I would normally have without the handler. Now I could simply return the exit code you would normally get but the problem was that I don't know the exact exit code in these cases (as seen in the thread I linked to and the post from meuh it's quite complicated). Your post was still helpful, I didn't think about redefining the trap handler dynamically. – phk Oct 12 '15 at 14:09

Actually, interrupting bash's internal read seems to be a bit different to interrupting a command run by bash. Normally, when you enter trap, $? is set and you can preserve it and exit with the same value:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

If your script is interrupted when executing a command like sleep or even a builtin like wait, you will see

130 EXIT

and the exit code is 130. However, for read -p, it seems $? is 0 (on my version of bash 4.3.42 anyway).

The handling of signals during read might be work in progress, according to the changes file in my release... (/usr/share/doc/bash/CHANGES)

changes between this version, bash-4.3-alpha, and the previous version, bash-4.2-release.

  1. New Features in Bash

    r. When in Posix mode, `read' is interruptible by a trapped signal. After running the trap handler, read returns 128+signal and throws away any partially-read input.

  • OK, that's really odd. It's 130 on the interactive shell on bash 4.3.42 (under cygwin), 0 under the same shell when in a script and POSIX mode or not makes no difference. But then under dash and busybox it's always 1. – phk Oct 12 '15 at 13:28
  • I tried the POSIX mode with a trap that doesnt exit, and it restarts the read, as noted in the CHANGES file (added to my answer). So, it might be work in progress. – meuh Oct 12 '15 at 13:45
  • The exit code 130 is 100% non-portable. While the Bourne Shell uses 128 + signo as the exit code for signals, ksh93 uses 256 + signo. POSIX says: something above 128.... – schily Oct 12 '15 at 14:22
  • @schily True, as noted in the thread I linked to in the original post (unix.stackexchange.com/questions/99112). – phk Oct 12 '15 at 14:23

Any usual signal exit code will be available in $? upon entry to the trap handler:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
trap sig_handler INT HUP TERM QUIT

If there is a separate EXIT trap, you can use the same methodology: immediately keep the exit status passed from the signal handler (if there is one) clean-up, then return the saved exit status.

  • This applies to most shells? Very nice. local isn't POSIX though. – phk Sep 30 '18 at 18:41
  • 1
    Edited. I've not tested in other than {ba,z}sh. AFAIK, it's the best that can be done, regardless. – Tom Hale Oct 1 '18 at 6:54
  • This doesn't work in dash ($? is 1 whatever the received signal is). – MoonSweep Mar 20 at 10:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.