1

Can someone explain me why the script

echo one && 
echo two & 
echo three && 
echo four

gives me

[1] 3692
three
four
one
two
[1]+  Done                    echo one && echo two

Apparently everything behind the background process is outputted first and the lines of code chronologically before the background process are printed last. I stumbled upon it in a script I tried to write and don't get why it behaves like this (althogh I guess there is some ingenuity behind it).

I already found out that I can prevent this with brackets:

echo one && 
(echo two &) 
echo three && 
echo four

gives

one
two
three
four

and

echo one && 
(sleep 2 && echo two &)
echo three && 
echo four

gives

one
three
four
two

Because line two sleeps in the background and hence outputs when the first lines have already been executed.

But why do brackets have this effect?

Bonus question: Why do brackets prevent the output of the background PID?

nnn
  • 1,181

2 Answers2

1

Part 1

echo one && 
echo two & 
echo three && 
echo four

This can be rewritten as

echo one && echo two & 
echo three && echo four

The reason you get the three and four first is simply that it takes longer to get the subshell handling the one and two up and running than it does to print the three and four.

You can see this a little more clearly like this

echo one && echo two &
sleep 1
echo three && echo four

In this situation you get one and two, followed a second later by three and four.

Note that in the original scenario there is no guarantee that the one and two won't be mixed in with the output of three and four, conceiveably even with words being interpolated, such as thonreee or twfouro.

Part 2

echo one && 
(echo two &) 
echo three && 
echo four

This can be rewritten as

echo one && (echo two &) 
echo three && echo four

Here what is happening is that the one is printed immediately, and then the two is fired off in a subshell. Then (serially) the next line is executed, resulting in three and four. In your particular situation the subshell is small and fast enough that the two can be printed before the three is output. No guarantee of this, though, either.

Part 3

The brackets group statements together into a subshell. The ampersand & applies to a statement, but in this case you have used && to link the two commands together so they have to be treated as a single statement.

Perhaps you should be using echo one; echo two rather than echo one && echo two? They are very different. The semicolon ; separates two commands, which then run independently but sequentially. The double ampersand && joins the two commands together as a logical AND, such that the second will only run if the first one completes successfully. Compare false; echo yes, false && echo yes, and true && echo yes. Then experiment by replacing && (logical AND) with || (logical OR).

Bonus

You lose the job control notification because the subshell doesn't have job control.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
1
echo one && 
echo two & 
echo three && 
echo four

Keep in mind that this is parsed like

echo one && echo two &
echo three && echo four

I'll assume that the echo commands are successful (they would only fail in edge cases such as redirection to a file on a full disk). So this snippet runs two tasks in parallel: one prints the lines one and two, and the other prints the lines three and four. The interspersing of one and two with three and four is not guaranteed and is likely to vary from system to system, from shell to shell and from invocation to invocation. For a toy example like this, you might observe reproducible results on a lightly-loaded system, but this is not something you can count on.

echo one && 
(echo two &) 
echo three && 
echo four

You've changed the parsing: here only the command echo two is executed in the background. This guarantees that one will be output first, and like before three will always be before four, but the placement of two could be anywhere after one.

The shell only prints a message about background processes if they're started from the main shell process, not when they're started in a subshell. Parentheses create a subshell, which is why (echo two &) doesn't cause the shell to print any message. This creates a background process, but not a background job.

echo one && 
(sleep 2 && echo two &)
echo three && 
echo four

In this snippet, the background job sleeps for 2 seconds before outputting two. This makes is extremely likely (but not actually guaranteed) that two will be output after three and four.