5

The way I normally start a long-running shell script is

% (nohup ./script.sh </dev/null >script.log 2>&1 & )

The redirections close stdin, and reopen stdout and stderr; the nohup stops HUP reaching the process when the owning process exits (I realise that the 2>&1 is somewhat redundant, since the nohup does something like this anyway); and the backgrounding within the subshell is the double-fork which means that the ./script.sh process's parent has exited while it's still running, so it acquires the init process as its parent.

That doesn't completely work, however, because when I exit the shell from which I've invoked this (typically, of course, I'm doing this on a remote machine), it doesn't exit cleanly. I can do ^C to exit, and this is OK – the process does carry on in the background as intended. However I can't work out what is/isn't happening to require the ^C, and that's annoying me.

The actions above seem to tick most of the boxes in the unix FAQ (question 1.7), except that I'm not doing anything to detach this process from a controlling terminal, or to make it a session leader. The setsid(2) call exists on FreeBSD, but not the setsid command; nor, as far as I can see, is there an obvious substitute for that command. The same is true on macOS, of course.

So, the questions are:

  • Is there a differently-named caller of setsid on this platform, that I'm missing?
  • What, precisely, is happening when I exit the calling shell, that I'm killing with the ^C? Is there any way this could bite me?

Related questions (eg 1, 2) either answer a slightly different question, or assume the presence of the setsid command.

(This question has annoyed me for years, but because what I do here doesn't actually not work, I've never before got around to investigating, getting stumped, and asking about it).

Norman Gray
  • 11,978
  • 2
  • 33
  • 56
  • What shell are you using? `csh` doesn't like your syntax and `sh` seems to exit without any problems at all. – Richard Smith Apr 30 '20 at 19:24
  • It's bash and zsh (certainly not *csh), and I don't think it's anything complicated to do with a WM, since the time I notice this is when I'm (finishing) sshing somewhere. It's bound to be something subtle, though... – Norman Gray Apr 30 '20 at 21:49
  • With `zsh` you may need to use `&!` instead of `&` to fully disown the job from the parent shell. – Richard Smith Apr 30 '20 at 22:12
  • True, `&!` does ‘background and disown’, but disowning is just about removing from the job table, so is not subject to the job control operations (as I write this, I become aware I've a slightly vague idea of just what the ‘job table’ represents, and what removal from it means). Also the process backgrounded as above _does_ disappear from the job table. – Norman Gray Apr 30 '20 at 22:22
  • 1
    There is a tiny (one C file) `setsid` program that I've been using on MacOSX (BSD-ish, also lacking a `setsid`) here: https://github.com/jerrykuch/ersatz-setsid – DouglasDD Aug 16 '20 at 14:47

2 Answers2

2

In FreeBSD, out of the box you could use daemon -- run detached from the controlling terminal. option -r could be useful:

-r       Supervise and restart the program after a one-second delay if it
         has been terminated.

You could also try a supervisor, for example immortal is available for both platforms:

pkg install immortal  # FreeBSD
brew install immortal # macOS

To daemonize your script and log (stdout/stderr) you could use:

immortal /path/to/your/script.sh -l /tmp/script.log

Or for more options, you could create a my-service.yml for example:

cmd: /path/to/script
cwd: /your/path
env:
    DEBUG: 1
    ENVIROMENT: production 
log:
    file: /tmp/app.log
stderr:
    file: /tmp/app-error.log

And then run it with immortal -c my-service.yml

More examples can be found here: https://immortal.run/post/examples

If just want to use nohup and save the stdout & stderr into a file, you could add this to your script:

#!/bin/sh
exec 2>&1

...

Check more about exec 2>&1 in this answers https://stackoverflow.com/a/13088401/1135424

And then simply call nohup /your/script.sh & and check the file nohup.out, from the man

FILES
       nohup.out   The output file of the nohup execution if stan-
                   dard  output  is  a terminal and if the current
                   directory is writable.

       $HOME/nohup.out The output file of the nohup execution if stan-
                   dard  output  is  a terminal and if the current
                   directory is not writable.
nbari
  • 25,603
  • 10
  • 76
  • 131
  • Thanks for that, @nbari: the `daemon` command looks interesting, I can see some cases where that'll be useful, and looking at [the source](https://github.com/freebsd/freebsd/blob/master/usr.sbin/daemon/daemon.c) I see it works by calling daemon(3). However the main motivation for my question was why the actions at the top – which do most of the job – aren't quite the full story. It's a ‘what am I misunderstanding?’ question at heart. – Norman Gray May 02 '20 at 22:54
0

There's daemontools-encore by DJB for running any program as a daemon, including shell scripts. He has a whole tool set for running TCP servers, with rate limiting, and user isolation.

Engineer
  • 834
  • 1
  • 13
  • 27