How to Start and Manage Background Processes in Shell Scripts

background-processcommand linejob-controlshell-scriptsignals

I'd like to run and configure a process similarly to a daemon from a script.
My shell is zsh emulated under Cygwin and the daemon is SFK, a basic FTP server.

For what matters here, the script startserv.sh can be drafted as follows:

#!/bin/sh
read -s -p "Enter Password: " pw
user=testuser
share=/fshare
cmd="sfk ftpserv -user=$user -pw=$pw -usedir $share=$share"
$cmd &

After running the script startserv.sh, it stops (ends?) without showing any prompt, then:

  • CTRL+C ends both the script and the background job process;

  • Hitting Enter the script ends the process remains in the background.

Anyway I can see it only via ps and not jobs, so, when I want to close the process, I have to send a brutal kill -9 signal, which is something I'd like to avoid in favour of CTRL+C.

An alternative would be running the whole script in background. 'Would be', but the read command is unable get the user input if the script is run as startserv.sh &.

Note that I need an ephemeral server, not a true daemon: that is, I want the server process to run in the background, after the script ends, to perform simple interactive shell tasks (with a virtual machine guest), but I don't need the process to survive the shell; therefore nohup seems not appropriate.

Best Answer

Hitting Enter the script ends the process remains in the background.

Almost! Actually, the script has already exited by the time you press Enter. However, that's how you can get your prompt back (because your shell prints its $PS1 all over again).

The reason why hitting Ctrl + C terminates both of them is because the two of them are linked. When you execute your script, your shell initiates a subshell in which to run it. When you terminate this subshell, your background process dies, probably from a SIGHUP signal.

Separate the script, the background process and the subshell

Using nohup, you might be able to get rid of this little inconvenience.

#!/bin/sh
read -s -p "Enter Password: " pw
user=testuser
share=/fshare
cmd="sfk ftpserv -user=$user -pw=$pw -usedir $share=$share"
nohup $cmd &

The disown alternative

If you can switch from /bin/sh to /bin/bash, you can give a try to disown as well. To know more, just type help disown in a bash instance.

disown -h $cmd &

Killing the background process "nicely"

Now, when it comes to killing your process, you can perfectly do a "Ctrl + C" using kill. Just don't send a brutal SIGKILL. Instead, you could use:

$ kill -2 [PID]
$ kill -15 [PID]

Which will send a nice SIGINT (2) or SIGTERM (15) to your process. You may also want to print the PID value after starting the process:

...
nohup $cmd &
echo $!

... or even better, make the script wait for a SIGINT, and send it back to the background process (this will keep your script in the foreground though):

#!/bin/sh
read -s -p "Enter Password: " pw
user=testuser
share=/fshare
cmd="sfk ftpserv -user=$user -pw=$pw -usedir $share=$share"
nohup $cmd &

# Storing the background process' PID.
bg_pid=$!

# Trapping SIGINTs so we can send them back to $bg_pid.
trap "kill -2 $bg_pid" 2

# In the meantime, wait for $bg_pid to end.
wait $bg_pid

If a SIGINT isn't enough, just use a SIGTERM instead (15) :

trap "kill -15 $bg_pid" 2 15

This way, when your script receives a SIGINT (Ctrl + C, kill -2) or a SIGTERM while waiting for the background process, it'll just relay the signals to it. If these signals do kill the sfk instance, then the wait call will return, therefore terminating your script as well :)

Related Question