In an interactive bash shell running inside lxterminal, if I run a background job

$ evince &

and then close the shell, the background job will be killed.

If I put the background command in a script, and run the script in an interactive bash shell:

$ cat test.sh 
!# /bin/bash

evince &
$ ./test.sh

after the script terminates, the background job still runs. Even after I terminates the interactive bash shell, the background job still continues running.

I wonder why putting a background job into a script and running the script can make the job survive both the termination of the script and the termination of the caller of the script? Is wrapping a job into a script a good way to make the job survive the termination of the script and the termination of the shell which invokes the script?


2 Answers 2

  1. By "close the shell", I assume you mean closing the lxterminal window. The lxterminal and the interactive bash are connected to each other over a pseudo-TTY device, which is a kernel-level construct.

    If you do that while its interactive bash child process is still running, the lxterminal process will close its side of the pseudo-TTY. That causes the kernel to send a SIGHUP signal to the interactive bash.

    The SIGNALS chapter of man bash says:

    The shell exits by default upon receipt of a SIGHUP. Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped. Stopped jobs are sent SIGCONT to ensure that they receive the SIGHUP.

    To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with the disown builtin (see SHELL BUILTIN COMMANDS below) or marked to not receive SIGHUP using disown -h.

    The intended meaning of the SIGHUP signal is "the connection to the user's terminal was lost; save any unsaved work if necessary, and then exit in an orderly fashion." (For daemon processes, this is not applicable, so for them the signal is often used to mean "reread your configuration files" and/or "close and reopen your log files for log rotation".)

    As the man page says, the interactive bash will then resend the SIGHUP signal to all its children (unless told otherwise using disown). This is what causes your backgrounded evince to exit.

  2. But when you use a script, your interactive shell fork()s a new process to run a shell to run the script, and then the new shell running the script fork()s yet again to run evince. After that, the script ends, so the shell running the script exits.

    At this point, the evince process will no longer have a parent process. But when you look at a ps -ef listing, there is never a process with an empty PPID field: it is impossible for the process not to have a parent. So, to deal with this situation, the kernel assigns the orphaned evince process PPID to 1, making it the adopted child of the init process. Now the original interactive bash does not know that the instance of bash that ran the script did start another process: this knowledge died along with the shell instance that ran the script.

    As a result, the interactive bash simply has no clue that there is an evince process that might need to have SIGHUP sent to it when the session ends.

    What you've achieved with that script is actually the bare-bones version of a technique used when intentionally daemonizing processes, known as a double fork. (Daemonizing a process = making it completely independent of the process and session that started it.)

    As you've seen, in practice, this is enough for making the evince process separate from the interactive bash but still keeping it a part of your GUI login session. But if you want a complete daemonization, there's a few other steps you should do before the second fork:

    • make sure that standard input, standard output and standard error are redirected to /dev/null
    • if any other file descriptors are open, close them
    • cd / so that your process won't accidentally interfere with the sysadmin mounting/unmounting filesystems, unless the process actually needs to access files in a particular filesystem
    • set umask explicitly to the value you want/need it to be
    • if setsid command is available, you should use it to disconnect the resulting process from your session's process group and make it fully separate (i.e. instead of evince & at the end of your script, you might use exec setsid evince).

    See here for a more detailed description of daemonizing in bash shell: http://blog.n01se.net/blog-n01se-net-p-145.html

  • Thanks. I took the liberty to edit your post to make it look even more clear to me. Feel free to let me know if I was wrong.
    – Tim
    May 5, 2018 at 10:27
  • In the first case $ evince &, the background job is running in a subshell of the invoking interactive shell. The background job is killed, when the invoking shell terminates, because the subshell is also interactive?
    – Tim
    May 5, 2018 at 11:05

The killing of the bacground evince it probably something in your logout profile.

using a script works because the shell that was running the the background task has already finished, you can get the same effect by typing.

( evince & )

That starts a subshell, the subshell backgrounds evince and then exits. evince is now a daemon.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .