I'm trying to write a Linux shell script (preferably bash),
supposedly named detach.sh
, to safely detach programs from a terminal,
such that:
Invocation:
./detach.sh prog [arg1 arg2 ...]
.Is
exec
-able, eg. by running this in your shell:exec ./detach.sh prog [arg1 arg2 ...]
With proper quoting (mainly handling of arguments containing whitespaces).
Discards the outputs (since they are unneeded).
Does not use
screen
,tmux
, etc. (same reason with 4, plus no need for an extra babysitting process).Uses (reasonably) portable commands and programs, and no things like
start-stop-daemon
which is quite distro-specific.
I have thought of several ways (shebang lines #!/bin/bash
neglected
for the sake of briefness):
nohup
:nohup "$@" >& /dev/null &
disown
:"$@" >& /dev/null & disown
setsid
:setsid "$@" >& /dev/null &
Using a subshell:
("$@" >& /dev/null &)
nohup
/setsid
combined with subshell:# Or alternatively: # (nohup "$@" >& /dev/null &) (setsid "$@" >& /dev/null &)
When using gedit
as the test program (substituting the "$@"
part),
condition 1 can be satisfied with all the above methods,
but condition 2 can be satisfied with none.
However, if an arbitrary program (but not a shell builtin) is appended to script 5,
all the conditions seem to be satisfied (at least for me in the gedit
case).
For example:
(setsid "$@" >& /dev/null &)
# Not just `true' because it is also a shell builtin.
/bin/true
Anyone with an idea about an explanation of the above phenomenons and how to correctly implement the requirements?
EDIT:
With condition 2, I mean the program should be detached from the terminal but runs as usual otherwise. For example, with the gedit
case, the condition fails if gedit
just exits immediately right after the process of the script has ended.
I think you need to do
setsid "$@" >& /dev/null & wait
so that the controlling terminal does not disappear beforesetsid
manages to fork the child.UPDATE
It seems to me that this works both on command line and as argument of
-c
:You are trying to create a UNIX daemon process (i.e., a process that has no controlling terminal and that is its own session leader). The
setsid
command should do this for you, but you are responsible for closing all file descriptors that are open on the terminal you are abandoning. This can be done by redirecting them to/dev/null
or using the shell's syntax for closing file descriptors (e.g.,2>&-
and0<&-
in Bash).Upon closer investigation, these previously unnoticed facts were revealed:
Both scripts 3 and 5 (the
setsid
variant only) will satisfy all the conditions if a/bin/true
is appended to the script.These scripts, as modified in fact 1, will work as well if
/bin/true
is replaced withfor i in {0..9999}; do :; done
.Therefore we can conclude that:
(From fact 1)
Multiple levels of detaching (as in script 5) is unnecessary, and the key is to use the right utility (
setsid
).(From fact 2)
A suitable delay before bash exit is necessary for the success of the script. (Calling external program
/bin/true
consumes some time, just like the pure-bash time consumerfor i in {0..9999}; do :; done
.)I have not looked at the source code, but I guess a possible explanation is that bash may exit before
setsid
finishes configuring the execution environment of the program to run, if an appropriate delay is not applied.And finally, an optimal solution should be
EDIT 1:
The necessity of a delay has been explained here. Many thanks to @wilx!
EDIT 2:
(Thanks to @MateiDavid) we seem to have forgotten to redirect the standard input, and a better way would be: