The difference between
{ echo err >&2; echo out >&1; } | :
... and
{ echo out >&1; echo err >&2; } | :
... is the order in which the two echo calls are made. The order matters only in terms of how quickly the first echo can execute.
The two sides of the pipeline, { ...; } and :, are started concurrently, and : does not read from its standard input stream. This means the pipe is torn down as soon as : terminates, which will be almost immediately.
If the pipe is torn down, there is no pipe buffer for echo out >&1 to write to. When echo tries to write to the non-existent pipe, the left-hand side of the pipeline dies from receiving a PIPE signal.
If it dies before echo err >&2 has executed, there will be no output.
So:
You will always get err as output from { echo err >&2; echo out >&1; } | : since echo err >&2 will not care about the state of the pipe.
You will sometimes get err as output from { echo out >&1; echo err >&2; } | : depending on whether : has terminated before echo out >&1 has the chance to run. If : terminates first, there will be no output due to the left-hand side getting killed prematurely.
I'm guessing that most people will not experience the non-deterministic behaviour of the second pipeline on most systems. Still, you have found combinations of operating systems and shell versions that show this. I have also replicated the non-deterministic behaviour under zsh on FreeBSD, even in a single shell session (although the shell seems to tend to one behaviour rather than the other, so I needed 10+ tries to finally see the switch).
debian:11.5container and both commands gave output. – muru Nov 11 '22 at 04:24>&1doesn't do anything. It's redirecting stdout to itself, which is a no-op. – Barmar Nov 11 '22 at 16:53