Skip to content

Instantly share code, notes, and snippets.

@izabera
Last active January 6, 2025 17:46
Show Gist options
  • Save izabera/7dcece1d6df9063d43d2b3b0a222e7bb to your computer and use it in GitHub Desktop.
Save izabera/7dcece1d6df9063d43d2b3b0a222e7bb to your computer and use it in GitHub Desktop.

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

@bswck
Copy link

bswck commented Nov 11, 2024

It never prefixes commands

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)

@izabera
Copy link
Author

izabera commented Nov 11, 2024

that's the section on [[…]]

the relevant section is https://www.gnu.org/software/bash/manual/bash.html#Pipelines

@bswck
Copy link

bswck commented Nov 11, 2024

But you make a statement on ! in general. Pipelines section is not relevant to ! in general, but to ! in pipelines.
Try to type ! true as the entire command yourself and see that it's wrong to say that it never prefixes commands, it prefixes pipelines.

@bswck
Copy link

bswck commented Nov 11, 2024

It's a normal unary operator, see section 6.5

@izabera
Copy link
Author

izabera commented Nov 11, 2024

sorry, you're conflating a lot of things

  • 6.5 is the section on shell arithmetic. the things inside arithmetic contexts are not commands, but ((...)) is a (compound) command
  • the things inside [[...]] are not commands, but [[...]] is a (compound) command
  • if you type ! true, you're creating a pipeline with a single command, and applying ! to the pipeline
  • since pipelines are sequences of one or more commands separated by | or |&, you can verify that ! does not prefix commands, because true | ! false is a syntax error

@bswck
Copy link

bswck commented Nov 11, 2024

Oh yeah, sorry, you're correct.
image
It would be good for me to check my knowledge more thoroughly before entering into a discussion about correctness.
Thanks!

@bswck
Copy link

bswck commented Nov 11, 2024

@izabera One more thing—thanks for pointing everything out patiently. Please keep up making good content!

@izabera
Copy link
Author

izabera commented Nov 12, 2024

🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment