In the interactive console, pressing ^C on zenity --info & fg closes the Zenity window. One can only use zenity --info & wait in a script. But ^C doesn't close the Zenity window in this case. Is there any way to make wait behave more like fg and make ^C work?


The easiest thing you can do is to trap the SIGINT (^C) and kill the background process from the handler:

#! /bin/sh
trap 'kill "$!"; exit 1' INT QUIT
zenity --info &

See this and other similar answers for an explanation for this behavior.

When calling your script from some shells like bash which implement the so-called "Wait and Cooperative Exit" (see here for a discussion), the expected way for a program to cleanly exit upon receiving a signal is by resetting the handler and then killing itself with the same signal:

#! /bin/sh
termsig(){ trap - "$1"; kill "$!"; kill -s "$1" "$$"; }
trap 'termsig INT' INT
trap 'termsig QUIT' QUIT 
zenity --info &

All this is of course quite gross and won't be able to do anything about children processes that zenity itself may have spawned, and will have to be adapted for more than one background process. The more obvious solution (trap - INT; zenity --info) & will not work with the /bin/sh from any Debian-derived distros (Ubuntu, etc) or any busybox-based systems.

Another solution would be run your program through a wrapper that allows you to reset the SIGINT disposition to default, eg. with perl:

#! /bin/sh
perl -e '$SIG{INT} = $SIG{QUIT} = "DEFAULT"; exec @ARGV' zenity --info &

But if you go that way, you may just as well re-write your whole script in perl or python ;-)

| improve this answer | |
  • Unlike the ping from the linked example, this script won't exit with a 0 status upon a signal, so a simple while ...; .do ... /script || break; done will do. Committing seppuku (by resetting the handler and re-sending itself the signal) won't help with that anyway and may run into other issues. – mosvy Apr 24 '19 at 13:51
  • I've added a seppuku variant too, though I really don't like it. – mosvy Apr 24 '19 at 22:33

In a non-interactive sh, commands run asynchronously with & have their SIGINT and SIGQUIT signals ignored.

That's a POSIX requirement, and goes back to the time when terminal job control (tcsetpgrp()...) didn't exist. Today, it generally gets in the way.

Another POSIX requirement and that comes from the same origin is that when a signal was ignored upon shell startup, it cannot be un-ignored with trap. Again, nowadays, that's unwanted and annoying behaviour.

zsh implements that latter requirement only partly. a trap - SIGNAL to restore the default disposition of an ignored SIGNAL is ignored, but trap handler SIGNAL installs the handler for SIGNAL even when that SIGNAL was ignored upon startup, so one can still restore the default disposition if they really want to with trap : SIGNAL; trap - SIGNAL.

As long as SIGINT was not ignored in the script (like when the script was run in background), you can do:

#! /bin/sh -
(trap - INT QUIT; exec zenity) & wait

To restore the default disposition for SIGINT and SIGQUIT.

However, though POSIX explicitly says it should work, it doesn't with a few sh implementations. In particular, not with dash and most other ash derivatives, ksh93, bosh or yash. So you may want to change the she-bang to #! /bin/bash - or #! /bin/zsh - or #! /bin/mksh - to make sure you have a POSIX-compliant shell in this instance.

If SIGINT was ignored upon startup of the shell, you'll need to use zsh to restore their disposition:

#! /bin/zsh -
trap : INT QUIT # noop handler to unignore
trap - INT QUIT # restore default disposition for the shell
(trap - INT QUIT; zenity) & wait

Example (here on Linux where the signal dispositions are exposed as bitmaps in /proc/<pid>/status):

$ sh -c 'grep SigIgn /proc/self/status'
SigIgn: 0000000000000000
$ sh -c 'grep SigIgn /proc/self/status & wait'
SigIgn: 0000000000000006 # (1<<(SIGINT-1) | 1<<(SIGQUIT-1))

$ zsh -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000000
$ bash -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000000
$ mksh -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000000

$ dash -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000006
$ ksh93 -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000006
$ yash -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000000006
$ bosh -c '(trap - INT QUIT; exec grep SigIgn /proc/self/status) & wait'
SigIgn: 0000000000100006  # also SIGTTIN

$ (bash -c 'grep SigIgn /proc/self/status' & wait)
SigIgn: 0000000000000006
$ (bash -c 'trap - INT QUIT; grep SigIgn /proc/self/status' & wait)
SigIgn: 0000000000000006

$ (zsh -c 'grep SigIgn /proc/self/status' & wait)
SigIgn: 0000000000000002 # not sure why the SIGQUIT was unignored
$ (zsh -c 'trap - INT QUIT; grep SigIgn /proc/self/status' & wait)
SigIgn: 0000000000000002
$ (zsh -c 'trap : INT QUIT; trap - INT QUIT; grep SigIgn /proc/self/status' & wait)
SigIgn: 0000000000000000
| improve this answer | |
  • Your first example only works with zsh, too. Why the #! /bin/sh shebang? – mosvy Apr 24 '19 at 6:44
  • @mosvy, it works with mksh, bash and zsh and should work in POSIX compliant implementations. I've added a note. – Stéphane Chazelas Apr 24 '19 at 6:45
  • The first sentence should probably be "In a sh run without job control enabled (as in a script or subshell), etc". Also, you really should change that shebang to #! /bin/bash -- that won't work with the /bin/sh from debian, ubuntu, busybox, etc; no matter if "entry to a non-interactive shell" in the standard is interpreted to include subshells or not. – mosvy Apr 24 '19 at 7:10

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.