14

I want to run two shell scripts after each other, in the background, preferably also using nohup.

I start with

% nohup a.sh &

This runs a in the background.

% nohup a.sh && nohup b.sh

This runs a and then b (if a succeeds)

But how do I launch the last combination in the background?

If I do

% nohup a.sh && nohup b.sh &

a will run in the foreground, blocking the prompt, and then b will be launched in the background, returning control to the prompt.

% nohup a.sh & && nohup b.sh &

This gives an error: zsh: parse error near '&&'

How do I launch two processes in the background?

d-b
  • 1,891
  • 3
  • 18
  • 30
  • 2
    Do you want the two scripts to run in parallel in the background, or does b.sh need to wait for a.sh to finish? – terdon Nov 10 '23 at 13:01
  • Not parallel. B depends on the output from A. – d-b Nov 10 '23 at 13:27
  • nohup is pointless in any job control shell (including zsh, bash, ksh, csh). – chicks Nov 14 '23 at 22:12
  • @chicks Is it? If I ssh to a computer, starts a script and the ssh connection is interrupted while the script is still running, the script is "killed", isn't it? nohup prevents that? – d-b Nov 14 '23 at 23:24
  • That's funny because my script isn't killed. I reverify this every few years because it seems to be such a surprise. Job control shells don't kill background jobs by default. – chicks Nov 15 '23 at 03:05

2 Answers2

27

That's one of the known deviations¹ of zsh compared to sh (or csh for that matters).

In sh:

A && B &

is short for:

(A && B) &

That is, it runs a subshell that runs that and-or list in background / asynchronously.

In zsh,

A && B &

Runs A synchronously, waits for it, and if successful, runs B in background / asynchronously.

That's noted in its manual (see info zsh list):

A list is a sequence of zero or more sublists, in which each sublist is terminated by ;, &, &|, &!, or a newline. This terminator may optionally be omitted from the last sublist in the list when the list appears as a complex command inside (...) or {...}. When a sublist is terminated by ; or newline, the shell waits for it to finish before executing the next sublist. If a sublist is terminated by a &, &|, or &!, the shell executes the last pipeline in it in the background, and does not wait for it to finish (note the difference from other shells which execute the whole sublist in the background). A backgrounded pipeline returns a status of zero.

(emphasis mine).

And in the FAQ:

In cmd1 && cmd2 &, only cmd2 instead of the whole expression is run in the background in zsh. The manual implies this is a bug. Use { cmd1 && cmd2 } & as a workaround.

To run a subshell that does A && B in background like in other shells, use:

(A && B) &

Or as suggested by that FAQ entry:

{A && B} &

Which are equivalent since we need to fork a process anyway if it has to run asynchronously (and the main shell process to carry on with the rest of the script at the same time).

So here:

(nohup a.sh && nohup b.sh) &

Though if you just want the process running that subshell and the other processes it spawns to be immune to the SIGHUP signal:

(trap '' HUP; A && B) &

(you can also add some < /dev/null >> nohup.out 2>&1 to even more closely mimic nohup)

Conversely, if you want the same behaviour as zsh's in other sh-like shells:

A && { B & }

(bearing in mind that in shells other than zsh, when job control is no enabled, the stdin of lists run asynchronously is redirected to /dev/null)


¹ It's possible that deviation may have been unintentional as the wording of the documentation before that 2000 change to fix it to align with the actual behaviour (and that goes back all the way to zsh 1.0 in 1990) described the sh behaviour. That's also the conclusion of the FAQ that has had that "The manual implies this is a bug" since 1997. But it would be too late to change it now (but one would argue it should be changed in sh, csh or ksh emulations).

8

You have to group the two commands together:

nohup sh -c 'a.sh; b.sh' &

Replace ; with && if you want b.sh to run only if a.sh exits with success

Chris Davies
  • 116,213
  • 16
  • 160
  • 287