> If the pipeline does not begin with the "!" reserved word, the exit status shall be the exit status of the last command specified in the pipeline. Otherwise, the exit status shall be the logical NOT of the exit status of the last command
Why take a perfectly readable if-statement and turn it into something, 99.9% of people would need to lookup. Concise != better. You can make it one line with:
I am not a huge fan of most of these, but a few do seem useful.
: "${1:?missing argument, aborting!}"
I wouldn't use this because I would want to give $1 a name for the rest of the script, so I would assign. But it can be a nice way to give a clear error for missing required environment variables.
Many of the others (like truncating files) are probably more clearly written with dedicated commands, but may come in useful if you are going to extreme lengths to avoid dependencies outside of the shell.
You are very much correct and I 100% agree with you, I have updated the first example to include a snippet where a proper env-var is used to show of the automatic diagnostic.
"Shortcuts" like :? that tie together two unrelated things (checking a variable for a condition, expanding the value of that variable) drive me up the wall. It makes expressing just one of the two things harder than expressing both, leading to contortions when you want just one, like this use of :.
$ ./script.ps1 Dave
Hello, Dave!
$ ./script.ps1 -name Dave
Hello, Dave!
$ ./script.ps1
cmdlet script.ps1 at command pipeline position 1
Supply values for the following parameters:
name: <cursor here>
Or non interactively:
$ pwsh -nonint ./script.ps1
script.ps1: Cannot process command because of one or more missing mandatory parameters: name.
PowerShell needs to become more ubiquitous. There are so many things about it that once considered in greater detail make so much sense. Parameter setup, typing, and naming. Flow control. Object-oriented scripting. Built-in parsing for recursive data types like JSON, HTML, XML.
And in my opinion, the most slept-on: the fact it runs on the CLR and direct access to .NET objects and types which means access to P/Invoke and thence the Windows API. One can write business logic in the fast language and write a nice CLI wrapper around that in the natural shell language, and not worry about painful FFI unlike everyone else trying to fit Python or Bash into whatever world they're using.
The typical counter to this will be: PowerShell is verbose, PowerShell used `curl` as an alias to Invoke-WebRequest instead of the Real Thing™. Neither are real arguments.
I wrote my first real `.ps1` the other day for auto-installing all dependencies needed to run a `gitea`-runner on windows for windows builds; powershell - felt like the lover I never had. And the documentation in readable comments at the top that just.. generates usable docs? Damn, damn, damn.
There is a part of me that low-key wanna try that as my daily driver for a week or two. But with that said, I'm a zsh vi-mode guy - always have been, always will be.. but I'd happily take powershell on a romantic getaway every once in a while!
PowerShell is a bad choice for the system shell because it's too complex. I'm not trying to justify all the quirks of Unix Shell, but minimalism is a very important feature of such a tool.
Another important feature of a tool like this is the ability to tolerate errors: I can't imagine a Linux today that would be able to even boot if the shell was extra pedantic about errors. A lot of mostly irrelevant things routinely fail on boot and during normal operation. Stamping them all out is an arduous... well, basically, an impossible task for practical purposes where releases are expected to come on time, where users may manipulate configuration in gazzilions of unpredictable ways.
PowerShell is just another language in the same box with Python, Perl, Ruby and many like that. It's not a good language, if you decided to reach for that box. Probably not the worst either.
System shell, however, isn't meant for writing entire applications. Writing applications with elaborate command-line interface should be left to languages that can properly address this problem. PowerShell is trying to be there, but it doesn't hold a candle to its "older brothers" who can, indeed, design a very robust command-line interface, often using a dedicated library for it.
PowerShell appeals to the novice crowd who are very enthusiastic about automatic checks in their code: the benefits are on the surface, the downsides are difficult to assess. This is in line with other Microsoft software products / languages which target novice programmers by implementing as many as possible of the highly-advertised features without regard to the overall usefulness of the product (think about C# or MS Office suit etc.)
As a go-to idiom for a writability test, it gives me pause. If the file didn't exist, we created a zero-length one. That might be okay if we are going to write to it anyway as the next action.
If we are testing because we intend to overwrite it, why not just "> result.json" (which is by itself an idiom for truncating a file to zero length).
When would we every do this? Maybe before some command which takes the file name as a destination file argument rather than using output redirection, and which performs a lengthy computation before trying to open the file for writing. We can catch the permission error early.
I don't think I've ever coded such a test; normally you just do the operation that writes to the file and let that fail.
In POSIX C, there is a function access() for doing these kinds of tests. But it has a special purpose: it is meant to be used by a setuid root process to perform a permission test as if it were the real user/group (the one which elevated privilege to root). I.e. it's not can we do this operation, but should we do this operation (would we still be allowed, if we dropped privileges back to the original user).
They are NOT superflous, and all you need to prove it is `zsh` (but there are others that follow suit in similar fashions):
zsh% echo "hello world" > data
zsh% < data && echo "READABLE" # <- will print the contents of data
hello world
READABLE
zsh% : < data && echo "READABLE" # <- this will not
READABLE
So, if you want something that "everyone can use" without going into details about the difference between commonly used shells.. you'd use the null-command.
---
and given that we use the null-command, it _WILL_ behave different with or without subshell.. and all you need is `bash --posix` to prove it:
% cat subshell.sh
#!/bin/bash --posix
( : < missing.json ); echo AFTER # <- will echo AFTER
% ./subshell.sh
./subshell.sh: line 2: missing.json: No such file or directory
AFTER
% cat no-subshell.sh
#!/bin/bash --posix
: < missing.json; echo AFTER # <- this will not
% ./no-subshell.sh
./no-subshell.sh: line 2: missing.json: No such file or directory
% : ^- apparently.. there is a difference
The output above is not truncated, `no-subshell.sh` will stop executing due to the broken read.
---
One should never trust things just because they are written, but that also applies to comments on HN. Originally when I read your message I actually thought I made a mistake, I was very close to writing an apology comment and adding a note to the blog post, but not close enough - I had to test it again.
I'm thankful for the watchful eyes and scrutiny when reading things online, that's good - keep it up, but your message is factually wrong - on so many levels.
It’s very common that the most upvoted comment on a post is a mistaken correction. People seem to love comments seemingly proving that the post is wrong without actually checking anything. And once it has enough upvotes a big discussion may follow up with personal attacks on the author or other questionable comments, while the people trying to point out that the post actually is accurate get either buried under the critic storm or downvoted to oblivion because people just don’t like the truth. This happens in every controversial topic.
I have never been in this situation before but I do agree with you, and honestly it feels very bad. I saw the comment when it was posted and, perhaps naively, thought "oh well, no one will care - its just another fluff comment".
Fast-forward to seeing that comment climb up the ranks, posted by a person who has 270x my karma, who seemingly gets upvotes by just.. writing things? no proof? no rationale? nothing?
Yeah, feels bad. I'm super happy so many are enjoying the article, and this situation can't take too much away from that, but man.. discouraging for sure.
Don’t worry, everyone who reads the comment will read yours as well, which is itself interesting.
> Yeah, feels bad. I'm super happy so many are enjoying the article, and this situation can't take too much away from that, but man.. discouraging for sure.
Again, don’t worry too much about that. The story got upvoted to the front page and lots of people will read it, that’s what matters. Not that a misguided post got a bunch of upvotes.
life is way too short to deal with this nightmare of a language and its 50000 footguns for anything longer than a 2 line script, especially in the age of LLMs. Just write a python/TS/any real language script instead. Bash is great for the command line, it should be limited to use there.
It turns out that LLMs are really good at writing bash too. even perl! maybe we should rethink some of these lost bits because we no longer need to worry about the arcane parts.
Define "really good?" I don't think I've ever had them produce a script that I didn't need to correct in some way. They can write it, that's true, but we still need to be able to read it.
Fair. I'm comparing it to the output I see generated in more mainstream, day to day, languages. I'm not going to bother to get into an argument on if the frontier models can generate amazing code in general.
What I've found is I can get get the frontier models to generate bash scripts, perl one liners, etc that do exactly what I need at roughly the same quality as any other code it generates.
100% They're super useful and I often use them to generate bash scripts of, but that's exactly how I know how important it is to check their work!
I'm a shell scripter at heart, the arcane stuff has always been a delight for me, so I've driven them to do some pretty complex stuff where previously I would have "copped out" and used python. I'd say the general adage of them being at the level of a very talent junior holds true.
I agree with you, instead of working on creating a bash script if we just sit as a senior developer ,check and correct what is needed is the way forward, it kind a remind me Pair programming when one do the scripting and other corrects it
Bash scripts requires "set -eu" at minimum, when written by LLM or human.
It's the only language terrible enough to make the default behavior ignore undefined variables, commands, and execution errors, and happily continue executing whatever was produced by me smashing my hands on the keyboard, until the end of the file, while returning an exit code of 0, claiming complete success.
> Bash scripts requires "set -eu" at minimum, when written by LLM or human.
No. It does not.
Just write good scripts and catch errors and handle them. That's the same more less with every language.
And bash can be written in a way that it is sane and readable and maintainable. Just because you have seen a lot of junk in the language does not make the language bad per se. Sure there are a lot of languages "features" which are more then questionable but I want to rise again the point that it has its place and can be used in a good way.
I have seen them write massively more complex bash scripts than any normal person would ever attempt, but the failure rate is absurdly high compared to when they write sane typed languages.
I find LLM's too fall into the trap of writing a bash script for a task that clearly needs to be implemented in an Actual Language with Real Data Structures. For example, ask an LLM to bring up a SQL server with some schema + data preload step, and it will write a profoundly long bash script to do that task, every time.
my experience as well. claude produces functional but over-engineered scripts fairly frequently with often very questionably useful safety checks, especially for powershell.
a common tell of ai generated powershell is a script that has dedicated functions to check types, often via several methods, and happily prints the output of the checks to shell. i do not get why it does this but it often adds dozens of lines that really serve no purpose but to make the shell output look fancy
It's frustrating when I catch it doing this because it's wasting tokens and time on what it reports as "one-off" scripts. Might add a requirement to never use shell scripting unless the task is truly a one liner.
Try being on linux and using zsh, it constantly fucks up scripts. Having it run commands via SSH into a windows machine is even more a comedy of errors.
I have to put in every claude.md that the shell is zsh. It's reached the point of annoyance I might just go back to bash.
> It's reached the point of annoyance I might just go back to bash.
You can use zsh as the default shell and still write your scripts for bash. Actually, it’s an advice I saw more than a couple of times. Otherwise you need to translate the bash-isms when you get bits of code from random places on the Internet.
Just put the right shebang (or ask Claude to do it). What’s the problem?
Just run your llm in a container with the tools it needs. It makes your pc more secure by removing the risk of doing something destructive to your computer and the llm can do llm. Also, llms ssh'ing to the outside world? Asking for trouble later on
I find that Claude does this much more than other models. GPT, for instance, knows I'm on Windows running PowerShell, and writes very idiomatic PowerShell.
But maybe that's because I'm the sucker, and since PowerShell is more verbose it costs me more tokens than the terseness of Unix shells. Oh well.
I usually skip the get option builtin, and use : as...
while : ; do
case "$1" in
"") break;;
-f|-foo) shift; whatever;;
*) usage; exit 1;;
esac
done
For this... instead
if something; then
true
else
echo ERROR
exit 1
fi
Using : would be too much here.
For anything else including json etc. I usually go to duckdb. Awesome support, single file install, readable, easy to maintain.
Powershell on Linux or Unix? Just another huge dependency if you manage 1000s of machines, and good luck finding a Linux gal/guy wanting or able to touch pwsh without chemical grade gloves.
I really hate bash because of its unreadable syntax, and this does not help it in any way.
We have some large bash scripts in my company, ~10,000 LOC spread across multiple files, all sourcing each other and what not. It is truly hard to read bash, which means it is truly hard to maintain bash, which means that when the one person knowing the bash scripts in your company goes away, you're in for some "fun".
My point is, these quirks are not useful, except for some bash enthusiasts.
The "if !" syntax isn't available on old enough shells.
Probably not an issue for most people in 2026 -- you have to back pretty far for it to be missing. Technically, though, "if x; then :; else" is more portable.
First of all, I must salute you on the joke; well put haha!
---
It is a (contrived) example of usage where a command is required and `:` can fill in the blanks. There are certainly scenarios where negating an expression becomes harder than doing the "dumb" way, and I for one has written code where `:` can be used as a placeholder meaning "fill this in later" or equivalent.
With that said, 100% agree with you that in actual "production" code - there is always a cleaner way.
> Though.. what if I told you the above four lines could be replaced by just... one?
I'd reject the pull request. Bash is already bad as programming language (the goodness of language for long code is inversely proportional to how nice it is for shell one-liners), this is just turning "bad" into "line noise"
If your bash script takes more than one screen, rewrite it in Python, hell, rewrite it in Perl, even that's better
I've read through all of the examples in the article & they all seem to serve to sole purpose of turning readable multiple line code into one-liners.
One-liners are a cool little artifact of early shell culture & are sometimes still useful today if they're short to avoid the readability problems of `/` when copy pasting a quick shell command to run, but they have no place in scripts.
None of this seems useful to me.
> if you are like me and prefer less typing (gotta go fast)
Bash scripts should only be used for quick and dirty tasks where brevity is a major benefit. Don’t pretend bash can be readable and maintainable. If you want that use another language that sacrifices brevity for clarity.
- it creates the file if it does not exist, not merely truncate. as a tutorial kind of blog post this incomplete description matters IMO.
- it would work the same without the colon (similar for default variable assignment examples). we generally strive not to have "extra" things, like useless use of cat.
- educationally it's useful to demonstrate that redirection, like parameter expansion, works before the command executes (the null command in this case), but the article doesn't explain that at all!
otherwise i <3 this article. some uses of colon i had never thought of or seen before. like file truncation, not sure i'd use them but it was cool to see them.
This is an excellent article that helps people decide
against writing shell scripts. I abandoned doing so
shortly after I switched to linux. Since then I was
also using ruby. I still do not understand why people
would prefer shell scripts over ruby (or python). On
systems without ruby or python, one may see a benefit
in using shell scripts; other than that I fail to see
why shell scripts are necessary.
Shell scripts simply suck for many reason. They are
ugly, verbose, convoluted, outright stupid too such
as argument passing into functions. Then there is
straight up retarded stuff such as case/esac. Whoever
came up with that was clearly an incompetent language
designer.
However, the only one I already knew...
I used to do that until I learned of It's in the POSIX standard so it's not just a bashism: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...> If the pipeline does not begin with the "!" reserved word, the exit status shall be the exit status of the last command specified in the pipeline. Otherwise, the exit status shall be the logical NOT of the exit status of the last command
I have an alias[1] for that which I call a quick interactive rebase:
[1]: https://github.com/fphilipe/dotfiles/blob/94f2ff70bade070694...Many of the others (like truncating files) are probably more clearly written with dedicated commands, but may come in useful if you are going to extreme lengths to avoid dependencies outside of the shell.
You are very much correct and I 100% agree with you, I have updated the first example to include a snippet where a proper env-var is used to show of the automatic diagnostic.
Thanks for your feedback, much much appreciated!
> Why use the null-command when I could do VAR=${VAR:-default-value}?
and points out it's one less thing to typo, but that assumes the name is the same; I like e.g.
And in my opinion, the most slept-on: the fact it runs on the CLR and direct access to .NET objects and types which means access to P/Invoke and thence the Windows API. One can write business logic in the fast language and write a nice CLI wrapper around that in the natural shell language, and not worry about painful FFI unlike everyone else trying to fit Python or Bash into whatever world they're using.
The typical counter to this will be: PowerShell is verbose, PowerShell used `curl` as an alias to Invoke-WebRequest instead of the Real Thing™. Neither are real arguments.
I wrote my first real `.ps1` the other day for auto-installing all dependencies needed to run a `gitea`-runner on windows for windows builds; powershell - felt like the lover I never had. And the documentation in readable comments at the top that just.. generates usable docs? Damn, damn, damn.
There is a part of me that low-key wanna try that as my daily driver for a week or two. But with that said, I'm a zsh vi-mode guy - always have been, always will be.. but I'd happily take powershell on a romantic getaway every once in a while!
Another important feature of a tool like this is the ability to tolerate errors: I can't imagine a Linux today that would be able to even boot if the shell was extra pedantic about errors. A lot of mostly irrelevant things routinely fail on boot and during normal operation. Stamping them all out is an arduous... well, basically, an impossible task for practical purposes where releases are expected to come on time, where users may manipulate configuration in gazzilions of unpredictable ways.
PowerShell is just another language in the same box with Python, Perl, Ruby and many like that. It's not a good language, if you decided to reach for that box. Probably not the worst either.
System shell, however, isn't meant for writing entire applications. Writing applications with elaborate command-line interface should be left to languages that can properly address this problem. PowerShell is trying to be there, but it doesn't hold a candle to its "older brothers" who can, indeed, design a very robust command-line interface, often using a dedicated library for it.
PowerShell appeals to the novice crowd who are very enthusiastic about automatic checks in their code: the benefits are on the surface, the downsides are difficult to assess. This is in line with other Microsoft software products / languages which target novice programmers by implementing as many as possible of the highly-advertised features without regard to the overall usefulness of the product (think about C# or MS Office suit etc.)
The subshell execution parentheses and the colon are superfluous here, just:
Redirections do not require a colon command to hang off of, and there is no need to fork a subshell to execute such a command.> ( : >> result.json ) && echo YES # is result.json writable?
As a go-to idiom for a writability test, it gives me pause. If the file didn't exist, we created a zero-length one. That might be okay if we are going to write to it anyway as the next action.
If we are testing because we intend to overwrite it, why not just "> result.json" (which is by itself an idiom for truncating a file to zero length).
When would we every do this? Maybe before some command which takes the file name as a destination file argument rather than using output redirection, and which performs a lengthy computation before trying to open the file for writing. We can catch the permission error early.
I don't think I've ever coded such a test; normally you just do the operation that writes to the file and let that fail.
In POSIX C, there is a function access() for doing these kinds of tests. But it has a special purpose: it is meant to be used by a setuid root process to perform a permission test as if it were the real user/group (the one which elevated privilege to root). I.e. it's not can we do this operation, but should we do this operation (would we still be allowed, if we dropped privileges back to the original user).
---
and given that we use the null-command, it _WILL_ behave different with or without subshell.. and all you need is `bash --posix` to prove it:
The output above is not truncated, `no-subshell.sh` will stop executing due to the broken read.---
One should never trust things just because they are written, but that also applies to comments on HN. Originally when I read your message I actually thought I made a mistake, I was very close to writing an apology comment and adding a note to the blog post, but not close enough - I had to test it again.
I'm thankful for the watchful eyes and scrutiny when reading things online, that's good - keep it up, but your message is factually wrong - on so many levels.
One of the reasons I stopped writing.
Fast-forward to seeing that comment climb up the ranks, posted by a person who has 270x my karma, who seemingly gets upvotes by just.. writing things? no proof? no rationale? nothing?
Yeah, feels bad. I'm super happy so many are enjoying the article, and this situation can't take too much away from that, but man.. discouraging for sure.
> Yeah, feels bad. I'm super happy so many are enjoying the article, and this situation can't take too much away from that, but man.. discouraging for sure.
Again, don’t worry too much about that. The story got upvoted to the front page and lots of people will read it, that’s what matters. Not that a misguided post got a bunch of upvotes.
What I've found is I can get get the frontier models to generate bash scripts, perl one liners, etc that do exactly what I need at roughly the same quality as any other code it generates.
I'm a shell scripter at heart, the arcane stuff has always been a delight for me, so I've driven them to do some pretty complex stuff where previously I would have "copped out" and used python. I'd say the general adage of them being at the level of a very talent junior holds true.
It's the only language terrible enough to make the default behavior ignore undefined variables, commands, and execution errors, and happily continue executing whatever was produced by me smashing my hands on the keyboard, until the end of the file, while returning an exit code of 0, claiming complete success.
Perl (needs use strict)
Ancient VB/VBA/VB script
Original PHP (no idea about modern)
PowerShell
Old Windows/DOS batch
No. It does not. Just write good scripts and catch errors and handle them. That's the same more less with every language. And bash can be written in a way that it is sane and readable and maintainable. Just because you have seen a lot of junk in the language does not make the language bad per se. Sure there are a lot of languages "features" which are more then questionable but I want to rise again the point that it has its place and can be used in a good way.
a common tell of ai generated powershell is a script that has dedicated functions to check types, often via several methods, and happily prints the output of the checks to shell. i do not get why it does this but it often adds dozens of lines that really serve no purpose but to make the shell output look fancy
I have to put in every claude.md that the shell is zsh. It's reached the point of annoyance I might just go back to bash.
You can use zsh as the default shell and still write your scripts for bash. Actually, it’s an advice I saw more than a couple of times. Otherwise you need to translate the bash-isms when you get bits of code from random places on the Internet.
Just put the right shebang (or ask Claude to do it). What’s the problem?
But maybe that's because I'm the sucker, and since PowerShell is more verbose it costs me more tokens than the terseness of Unix shells. Oh well.
The knowledge that Bash is awful and should be avoided as much as possible is surprisingly and disappointingly rare.
while : ; do case "$1" in "") break;; -f|-foo) shift; whatever;; *) usage; exit 1;; esac done
For this... instead
Using : would be too much here.For anything else including json etc. I usually go to duckdb. Awesome support, single file install, readable, easy to maintain.
Powershell on Linux or Unix? Just another huge dependency if you manage 1000s of machines, and good luck finding a Linux gal/guy wanting or able to touch pwsh without chemical grade gloves.
We have some large bash scripts in my company, ~10,000 LOC spread across multiple files, all sourcing each other and what not. It is truly hard to read bash, which means it is truly hard to maintain bash, which means that when the one person knowing the bash scripts in your company goes away, you're in for some "fun".
My point is, these quirks are not useful, except for some bash enthusiasts.
Probably not an issue for most people in 2026 -- you have to back pretty far for it to be missing. Technically, though, "if x; then :; else" is more portable.
---
It is a (contrived) example of usage where a command is required and `:` can fill in the blanks. There are certainly scenarios where negating an expression becomes harder than doing the "dumb" way, and I for one has written code where `:` can be used as a placeholder meaning "fill this in later" or equivalent.
With that said, 100% agree with you that in actual "production" code - there is always a cleaner way.
Also, I'll never use it.
Because a language feature that needs marketing is against readability, among those in my target audience who have not yet read the marketing.
I need my shell scripts to be long enough to explain to my audience exactly what they are doing.
I'd reject the pull request. Bash is already bad as programming language (the goodness of language for long code is inversely proportional to how nice it is for shell one-liners), this is just turning "bad" into "line noise"
If your bash script takes more than one screen, rewrite it in Python, hell, rewrite it in Perl, even that's better
One-liners are a cool little artifact of early shell culture & are sometimes still useful today if they're short to avoid the readability problems of `/` when copy pasting a quick shell command to run, but they have no place in scripts.
None of this seems useful to me.
> if you are like me and prefer less typing (gotta go fast)
Yeah, no.
- it creates the file if it does not exist, not merely truncate. as a tutorial kind of blog post this incomplete description matters IMO.
- it would work the same without the colon (similar for default variable assignment examples). we generally strive not to have "extra" things, like useless use of cat.
- educationally it's useful to demonstrate that redirection, like parameter expansion, works before the command executes (the null command in this case), but the article doesn't explain that at all!
otherwise i <3 this article. some uses of colon i had never thought of or seen before. like file truncation, not sure i'd use them but it was cool to see them.
I agree with you, and for what it's worth the truncation snippet is very much tongue-in-cheek much like `( : >> output ) && echo "is writable"`.
I wasn't expecting anyone to actually use these in Prod, rather I aimed to show what can be done with a command designed to.. do nothing (crazy).
Happy you enjoyed the article, and thank you!
Shell scripts simply suck for many reason. They are ugly, verbose, convoluted, outright stupid too such as argument passing into functions. Then there is straight up retarded stuff such as case/esac. Whoever came up with that was clearly an incompetent language designer.
https://en.wikipedia.org/wiki/Stephen_R._Bourne