7

I'm using a bash script script.sh containing a command cmd, launched in background:

#!/bin/bash
…
cmd &
…

If I open a terminal emulator and run script.sh, cmd is properly executed in background, as expected. That is, while script.sh has ended, cmd continues to run in background, with PPID 1.

But, if I open another terminal emulator (let say xfce4-terminal) from the previous one (or at the beginning of desktop session, which is my real use case), and execute script.sh by

xfce4-terminal -H -x script.sh

cmd is not properly executed anymore: It is killed by the termination of script.sh. Using nohup to prevent this is not sufficient. I am obliged to put a sleep command after it, otherwise cmd is killed by the termination of script.sh, before being dissociated from it.

The only way I found to make cmd properly execute in background is to put set -m in script.sh. Why is it necessary in this case, and not in the first one? Why this difference in behaviour between the two ways of executing script.sh (and hence cmd)?

I assume that, in the first case, monitor mode is not activated, as one can see by putting set -o in script.sh.

Glyph
  • 135
  • 1
    Note: file extensions of executables is bad practice. (what happens to calling scripts if you re-write in a different language?) – ctrl-alt-delor Nov 10 '18 at 14:01
  • xterm -e nohup ./script works for me. Maybe an xfce issue? – stefan Nov 10 '18 at 16:40
  • @stefan No, you're cheating ;) This works also for me with xfce4-terminal or gnome-terminal. The point is that nohup has to be putted in the script, we don't want all the script being affected by nohup, but just cmd. That said, it is interesting that it works in this case, without changing anything inside the script. – Glyph Nov 10 '18 at 17:06

2 Answers2

6

The process your cmd is supposed to be run in will be killed by the SIGHUP signal between the fork() and the exec(), and any nohup wrapper or other stuff will have no chance to run and have any effect. (You can check that with strace)

Instead of nohup, you should set SIGHUP to SIG_IGN (ignore) in the parent shell before executing your background command; if a signal handler is set to "ignore" or "default", that disposition will be inherited through fork() and exec(). Example:

#! /bin/sh
trap '' HUP    # ignore SIGHUP
xclock &
trap - HUP     # back to default

Or:

#! /bin/sh
(trap '' HUP; xclock &)

If you run this script with xfce4-terminal -H -x script.sh, the background command (xclock &) will not be killed by the SIGHUP sent when script.sh terminates.

When a session leader (a process that "owns" the controlling terminal, script.sh in your case) terminates, the kernel will send a SIGHUP to all processes from its foreground process group; but set -m will enable job control and commands started with & will be put in a background process group, and they won't signaled by SIGHUP.

If job control is not enabled (the default for a non-interactive script), commands started with & will be run in the same foreground process group, and the "background" mode will be faked by redirecting their input from /dev/null and letting them ignore SIGINT and SIGQUIT.

Processes started this way from a script which once run as a foreground job but which has already exited won't be signaled with SIGHUP either, since their process group (inherited from their dead parent) is no longer the foreground one on the terminal.

Extra notes:

The "hold mode" seems to be different between xterm and xfce4-terminal (and probably other vte-based terminals). While the former will keep the master side of the pty open, the latter will tear it off after the program run with -e or -x has exited, causing any write to the slave side to fail with EIO. xterm will also ignore WM_DELETE_WINDOW messages (ie it won't close) while there are still processes from the foreground process group running.

  • I think you're onto something. But I'm not seeing why xterm would be sending a SIGHUP to script.sh in the first example from the OP. The OP doesn't say the terminal was closed – iruvar Nov 11 '18 at 02:39
  • Great, this is a step forward! I have a better understanding of the behavior of the second command now. But I don't clearly see why, in the first case, we don't need anything to make cmd properly execute in background. Is it linked to the interactive nature of the first parent shell? Is there some implicit inheritance from it concerning SIGHUP? – Glyph Nov 11 '18 at 12:39
  • in the case where ./script.sh is not the main program on the terminal, but it's started as a foreground job from an interactive shell, its process group (also inherited by a cmd & started from within it) will stop being the foreground process group in the terminal as soon as it has exited. SIGHUP is only sent to processes from the foreground process group. –  Nov 11 '18 at 20:47
  • you can check all that from another window with ps t /dev/pts/6 o pid,pgid,tpgid,sid,args (replace /dev/pts/6 with the actual pty). If pgid=tpgid, then this process will receive a SIGHUP. If pid=sid then this is the session leader/controlling process, and pid=sid=pgid=tpgid, then it's "idle" ie it's running itself in foreground. –  Nov 11 '18 at 21:23
  • ok! I finally understand: all depends on what are exactly these "foreground process group" and "session leader". Thanks! I mark the question as resolved. – Glyph Nov 12 '18 at 00:22
  • I am wondering if there is a command like nohup, that will also put the process in to background after it has done the nohupping (I don't like having a special-case or race-condition). – ctrl-alt-delor Nov 12 '18 at 10:25
  • 1
    @ctrl-alt-delor isn't my example doing just that? you can make it into a shell function: bg_nohup(){ (trap '' HUP; "$@" &) } –  Nov 12 '18 at 10:45
0

I have been playing with this. I can't figure it out, but here is what I have discovered. I used sleep 3 as my cmd.

These two work. I have no idea what the ls at the end does. The brackets and double nohup, well they make a sub-process (I think that is how nohup works). I have no idea why nohup on its own does not work.

#!/bin/bash

nohup nohup sleep 3 &
ls #no idea why I need this

and

#!/bin/bash

(
nohup sleep 3 &
ls #no idea why I need this
)

/bin/true works in place of ls. Seems like we need to call an external command. Still no idea why.

  • I think that ls, /bin/true or other external command acts as sleep n, in the sense that it gives sufficient time to cmd for being properly launched in background. Indeed, in my case, ls does not work every time: it depends on its execution time (/bin/true works more often, but not always). Likewise, I don't systematically need a double nohup or brackets to make it works, so I think that all of this comes down to giving some time. – Glyph Nov 10 '18 at 15:52
  • what about disown -a – Bob Johnson Nov 12 '18 at 01:57
  • @BobJohnson how do you propose using disown -a? I see no advantage in by tests. – ctrl-alt-delor Nov 12 '18 at 10:17