3

I have written a script that relies on other server responses (uses wget to pull data), and I want it to always be run in the background unquestionably. I know one solution is to just write a wrapper script that will call my script with an & appended, but I want to avoid that clutter.

Is there a way for a bash (or zsh) script to determine if it was called with say ./foo.sh &, and if not, exit and re-launch itself as such?

tripleee
  • 175,061
  • 34
  • 275
  • 318
jparanich
  • 8,372
  • 4
  • 26
  • 34

5 Answers5

8

The definition of a background process (I think) is that it has a controlling terminal but it is not part of that terminal's foreground process group. I don't think any shell, even zsh, gives you any access to that information through a builtin.

On Linux (and perhaps other unices), the STAT column of ps includes a + when the process is part of its terminal's foreground process group. So a literal answer to your question is that you could put your script's content in a main function and invoke it with:

case $(ps -o stat= -p $$) in
  *+*) main "$@" &;;
  *) main "$@";;
esac

But you might as well run main "$@" & anyway. On Unix, fork is cheap.

However, I strongly advise against doing what you propose. This makes it impossible for someone to run your script and do something else afterwards — one would expect to be able to write your_script; my_postprocessing or your_script && my_postprocessing, but forking the script's main task makes this impossible. Considering that the gain is occasionally saving one character when the script is invoked, it's not worth making your script markedly less useful in this way.

If you really mean for the script to run in the background so that the user can close his terminal, you'll need to do more work — you'll need to daemonize the script, which includes not just backgrounding but also closing all file descriptors that have the terminal open, making the process a session leader and more. I think that will require splitting your script into a daemonizing wrapper script and a main script. But daemonizing is normally done for programs that never terminate unless explicitly stopped, which is not the behavior you describe.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • Hi Gilles, thanks for the detailed response. The solution you provide works along with ZyX's, but I accepted his as it is more self-contained and doesn't rely on ps or any branching. For the record, the script is solely for myself, will never be touched by another user and is in /etc/init.d so it'll not have terminal closure. Thanks =) +1 – jparanich Aug 31 '10 at 22:16
1

the "tty" command says "not a tty" if you're in the background, or gives the controlling terminal name (/dev/pts/1 for example) if you're in the foreground. A simple way to tell.

vince
  • 19
  • 1
  • This doesn't seem to be the case for me: a shell script which calls `tty` returns a terminal for me regardless of whether I invoke the script in the foreground or put a `&` at the end of it. – Smylers Sep 11 '13 at 15:35
1

I do not know, how to do this, but you may set variable in parent script and check for it in child:

if [[ -z "$_BACKGROUNDED" ]] ; then
    _BACKGROUNDED=1 exec "$0" "$@" & exit
fi
# Put code here

Works both in bash and zsh.

ZyX
  • 52,536
  • 7
  • 114
  • 135
  • I'm going to accept this answer, it seems to do what I want, except in your solution "endif" should be "fi" ;-) Thanks very much. – jparanich Aug 31 '10 at 22:13
  • Extra thought: I realize a fussy person could argue this snipplet will re-launch the script even if backgrounded at invocation, but the extra complexity of checking this isn't required for me. – jparanich Aug 31 '10 at 22:20
  • 1
    Note that `exec $0 $@` will make a mess of your arguments. Quoting is needed to be safe: `exec "$0" "$@"` -- otherwise you get `./yourprogram "argument one" "argument two"` run the same as `./yourprogram "argument" "one" "argument" "two"` -- and that's before we even start talking about wildcards. – Charles Duffy Apr 17 '21 at 16:56
  • @CharlesDuffy Sorry, was too used to zsh at that point, but not aware of some corner cases. You do not need extra quotes in zsh for the reason you stated (assuming default zsh options), except that just to make everybody's life harder not quoting `$@` will remove all arguments equal to empty string and not quoted `$0` if `$0` expands to an empty string will act as if it was not present. I.e. no problems from wildcards or spaces or anything else, but you still need to quote because of empty strings. – ZyX May 23 '21 at 17:37
-1

Remember that you can't (or, not recommended to) edit the running script. This question and the answers give workarounds.

Community
  • 1
  • 1
teika kazura
  • 1,348
  • 1
  • 16
  • 21
-4

I don't write shell scripts a long time ago, but I can give you a very good idea (I hope). You can check the value of $$ (this is the PID of the process) and compare with the output of the command "jobs -l". This last command will return the PID of all the backgrounded processes (jobs) and if the value of $$ is contained in the result of the "jobs -l", this means that the current script is running on background.

carlson
  • 15
  • 1
  • 1
    `jobs -l` cannot access job list of a parent process, check the output of `zsh -c 'jobs -l; echo $$; sleep 1s' &`: it will echo nothing except `$$`. – ZyX Aug 31 '10 at 16:36
  • I wrote a little shell script to test what I said, and I wrote jobs -l and echo $$. When I runned my script in fg, without & of course jobs -l didn't return nothing, but when I runned in bg it returned the same as $$... I am using bash – carlson Aug 31 '10 at 17:46
  • 1
    How do you run your script? I do not see this behavior neither in bash nor in zsh. – ZyX Aug 31 '10 at 18:06