This is from man bash:
A pipeline is a sequence of one or more commands separated by one of the control operators | or |&. The format for a pipeline is:
[ time [ -p ]] [ ! ] command [ [ | | |& ] command2 ... ]
This is the only place in which ! can appear. It never prefixes commands, it prefixes pipelines. As a consequence, this is fine:
! true | false
But this is a syntax error:
true | ! false
With that in mind, please guess what happens in this case:
! ! (exit 33); echo $?
A. Parser error because ! is a prefix to a pipeline and you can't just have 2 of them (same kind of error as above).
B. Prints 0 because ! is a boolean flag on the pipeline and adding it more than once only sets it to true repeatedly.
C. Prints 1 because ! is a flag on the pipeline and this is parsed as multiple nested pipelines and you're getting the exit code of the outermost one.
D. Prints 33 because ! is a flag on the pipeline and this is optimised into a single pipeline so the two cancel out.
Answer (spoiler!!!)
It prints 33 in bash. This is an extension to posix because that syntax is not defined in a posix shell. Collapsing the two is a very sane and useful extension. I'm sure this is exactly what you expected.
Bonus round for extra credits: what will happen in different shells, such as dash/ksh93/mksh/zsh?
Answer (spoiler!!!)
Syntax error in dash and zsh. Prints 1 in ksh93 and mksh.
Second bonus round for even more credits: what will these print in bash?
! { ! (exit 33); }; echo $? --- ${PIPESTATUS[*]}
! { ! (exit 33); } | :; echo $? --- ${PIPESTATUS[*]}
Answer (spoiler!!!)
! { ! (exit 33); }
This is valid syntax in a posix shell. Its exit code $? is required to be 1.
$PIPESTATUS however still shows 33.
! { ! (exit 33); } | :
This too is fully defined: it's a pipeline prefixed by !
Its exit code is required to be the negation of the status of the last component,
so $? is 1 because the exit code of :
is 0.
Things get a bit more interesting when looking at PIPESTATUS:
its contents are 0 0
.
This makes it clear that the two components of the pipeline were { ! (exit 33); }
(not ! { ! (exit 33); }
) and :
.
Both sides return 0 and they go into PIPESTATUS. Then finally the exit code is inverted so $? is 1.
next: reading a file line by line in your shell
previous: pipeline trick
How so?
!
can precede any expression.https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b (see list of operators at the bottom)