I used it (the linter + its wiki) last week to work on a bunch of related scripts, it was really helpful and even taught me some things about my bad habits.
Haha I assumed the second observation was a result of the first? Still, this is why one uses a reasonable distro. When I "apt-get install shellcheck", a version that's just about a year old (0.3.7) gets installed. Let package maintainers worry about compiling Haskell.
I was about to comment that year-old software is a downside, but given that it's for (ba|)sh maybe not so much; bash changes but glacially, and sh has barely moved in 20 years. Stability for the win :)
I don't know where the RAM requirement come from. I've been compiling shellcheck on VPS's with less than a gig RAM for a year and never hit a memory issue.
I'm the author of the article and ShellCheck. The RAM requirement comes from my own experience, where I had to add a swap file to my VPS because 1.7GB RAM wasn't enough.
I'm very surprised you've been getting by with less than a gig. What's your arch and GHC version, if you don't mind?
Older GHC versions appeared to use significantly less RAM, and I imagine 32bit pointers would save a lot.
Wow it's great to talk to you. Thanks for your great tool!
Your guess is right, I do my builds in Debian 8 with ghc and cabal from the repository. I do cabal update before compilation but I don't think this upgrades the ghc so the details should be like:
arch: i686
ghc: 7.6.3
I also use these arguments for cabal install but no idea if they affect anything:
"${bar}" is irritating to write. It only makes sense when you need to append
a word character ([a-zA-Z0-9_]) to the content of $bar, like in "${bar}qq".
Of course I have. [[ ]] is shell-specific, which for scripts almost always
means bash-specific, and bash has a history of subtly changing semantics of
such constructs. I've seen scripts suddenly stop working for no apparent
reason, only to discover later that bash upgrade changed how [[ == ]] treated
unanchored patterns (or something of similar caliber). And now go figure where
the bug is.
Most of the time, whatever is in [[ ]] can be easily emulated with
POSIX/SUS-compatible tools and most of the time it doesn't matter that the
tool is not a shell built-in.
Depends on what you mean by "works". Yes, all three shells recognize [[ ]]
construct, as it was specifically added to SUS (or POSIX?) for
implementation-specific things, but how they interpret it is up to the
specific shell.
Let's take people's favourite =~ operator. In bash, according to
documentation, it uses ERE syntax. In zsh, it is ERE or PCRE, depending on an
option being set. This is a subtle but important difference in how [[ ]]
works. And then again, your environment can change the behaviour of a script
you're running, which is (a) unexpected and (b) undesirable, and the change in
the behaviour can range from "doesn't find thing it should" to "crashes" to
"goes awry in an unexpected way".
My rule is that shell scripts with growing complexity must be converted to a language with clearer and more bulletproof treatment of data, like Python. This also happens any time I want to support several command line arguments.
At one point in history, options were more limited. Now, it is just not worth the time to read code that may or may not have obscure errors in its very expressions that you must understand before you can even begin to examine the real purpose of the program!
You still need to know how to write safe shell programs even if they're one-liners.
And once you do, shell is really, really useful and convenient.
For example, Linux distributions are full of shell scripts that would be frankly awful to write in any other language that I know of... like this, to take a random example:
I still disagree with the ideology that any non-trivial script needs to be rewritten in Python. The shell is the standard way of interacting with a *nix system, and for me that's the major benefit of using shell for "system"-ish tasks. I already know how to make a symlink, a pipeline, a chmod, etc, so I don't need to learn a new syntax for that.
The shell script probably also behaves strange if any of $out ${docker-runc} $version, $man, etc contains a space, while the python-script most likely wont.
Nix-generated paths don't contain spaces, so it's unnecessary to quote those variables. Of course, as a developer you have to understand this.
The semantics of shell string interpolation are generally not that hard to learn, compared to learning all the nuances of programming in general that can make your program incorrect, so I find it a bit strange how it's considered to make shell completely useless for scripting.
Shell scripting has been working pretty well since, like 1975! It's not that bad!
> Nix-generated paths don't contain spaces, so it's unnecessary to quote those variables
Which works as long as attacker (or sleepy admin) doesn't happen to create suitably "bad" path
> Shell scripting has been working pretty well since, like 1975! It's not that bad!
Considering the amount of borked system because spaces/uninitialized variables -stories I've heard over years, I'm not so sure about how well it has been working...
Namespaces are a honking great idea, let's do more of those!
Alternatively there's always the option of using something like sh[0] and just shelling out to the existing utilities, if you don't mind the portability loss.
> Not sure it is materially important to the argument
Not really, I've also forgotten to convert a few dashes to underscores in f-strings and probably made a few other mistakes along the way. It doesn't detract from the main point: it's a rough estimate of how such a converted script would look.
Though it makes me wonder if PyLint already has a lint for strings which start with F and have f-string placeholders.
>> once you do, shell is really, really useful and convenient
Part of the issue here is that a large number of people who think they know how to write safe shell scripts do not in fact have the ability to do so.
There are so many gotchas to learn that it's all too easy to believe oneself competent, while being unaware of just how short one's knowledge falls. It's just too easy to write a script in the shell of your choice that works - until it encounters an edge case scenario that wasn't planned for, at which point the results can be catastrophic. This is true of any programming language, but the tasks for which people write shell scripts (such as massive system-wide filesystem manipulation) makes the consequences of a single-line bug very real.
Glad you mentioned that, because that was going to be my reply after reading the first half of your post.
I'm not sure a person who is fairly well-versed in shell scripting is going to do a worse job than trying to use python, perl, lua, etc. if he doesn't also hsve a high level of experience and compentence in those languages.
It has been literally 10 years since I touched Python. I use shell scripting every day. I know which one I'm going to be better at using to produce reliable code that does what I want it to do.
Writing bug-free shell programs is hard because it doesn't provide safe defaults. By now I've gotten reasonably decent at shell scripts due to trial and error, and shellcheck... But if I'm doing anything fancy, I just opt for a node script using shelljs and any other third-party deps to cover my needs. This also has the benefit of typically working cross-platform. Although that's not as big of a concern for me, for some people it makes a big difference.
For my day-to-day shell, I use fish. IMO, it has much better defaults than bash and zsh, and it's fast. I still have to use bash sometimes, but it's becoming increasingly rare.
Your linked example doesn't really seem particularly tricky to me. I'd feel comfortable writing it in something like node or ruby, and I believe the result would probably be easier to consume for certain people.
There are a lot of unsafe defaults in JavaScript too, most prominently async error propagation. Basically all languages have some tricky fatal flaws, even Haskell (lazy I/O can make very strange bugs, etc).
I just don't think we should reject shell scripts entirely because some care is required to write them correctly. Shell is way too valuable.
You make a fair point. Although in more recent versions of JS the whole async error propagation isn't as big of an issue thanks to async functions and promises. I guess the biggest reason to use JS is that since I'm already using it every day, it's much easier to continue using a language I'm highly familiar with. Whenever I have to write a shell script I always have to pull up references and read through a couple Stack Overflow posts.
I don't reject shell scripts, though. I agree that in some cases it can lead to much simpler code. The power and composability with pipes and immediate substitutions is incredibly powerful. I've had a few cases where I replaced far more complicated node scripts with simpler shell scripts.
What I'd truly love is a reference guide that showed how to correctly do certain things with shell scripts. Here's a real-world example I struggled with: Writing end-to-end tests with selenium and a test runner. I wanted a script to start up a selenium server and wait for it to be ready. Then it should start the test runner. If the test runner exited successfully, kill the selenium server and browser, and exit with a 0. If the test runner or selenium server crashed or failed somehow, clean up and exit with a 1. Even after much trial and error, I still ended up with occasional Chrome copies staying open after failures.
Ah, for me one of the biggest reasons to use shell is that I use it every day to just do stuff in the terminal, so it's great to use that knowledge in scripts, too. But yeah, some of the syntax is quite obscure.
That kind of reference guide would be great. I haven't checked out any of the books on shell programming, but there should be a decent audience for a website with just good patterns for shell scripts, like you describe.
Trapping signals and keeping track of child PIDs is quite tricky, yeah. (It's quite tricky in any language!)
You might be interested in my "wd" program that's basically a bash wrapper for the WebDriver API, which I made because I wanted to write end-to-end tests and automation tools in shell.
Could you explain how writing, for example, text manipulation, would be safer in python than in shell? I understand formatting errors, shell escape failures, and the low hanging fruit, but in the end if I am just sed and awking my way around, I don't know how to do nearly the level of stuff I do in awk in python.
I also will never forget the time I spent a day doing a perl script to parse some data... took some time, came back, and did it in a one line awk... Very powerful.
I think somewhere in here is room for discussion of data, text, and the unix philosophy as everything as a file.
I said scripts with “growing complexity” should be expressed differently. I can and do use shell scripts for things that are relatively straightforward, and sed/awk-style transforms are a good example (although, even there, if too many quotes are involved you start to long for real escaping and things like triple-quotes).
You're assuming that shell and Python which do the same thing are equivalently readable to a proficient user. As someone who frequently uses both, I would strongly disagree with that. I have inherited lengthy shell scripts which probably took as long to read as it would have taken to learn Python.
I would argue that a well written Python script is going to be more comprehensible to the average sysadmin than an equivalent shell script, even assuming the sysadmin is more conversant in shell. More portable and testable (!!) too.
Shellcheck is a boon to write more (by at least three orders of magnitude) reliable shell code. I wish there were a 'set -o strict' that would make bash consider all shellcheck warnings into errors as well as unilaterally set -euo pipefail. That said, bash is still my go to language for many many things because at the end of the day it's insanely efficient as that koan[0] is still valid when substituting C for mostly any other language. As an anecdote I've replaced ridiculously big and slow Rakefiles and unmaintainable ad hoc ruby code with much shorter, readable and reliable shell scripts. Using the shell doesn't mean you can't drop a call to python, perl, jq or whatever, and the composability and performance can be terrific when properly wielded.
I agree. Shell scripts are awesome until you reach a certain threshold of complexity, and that threshold is... low.
Error handling is particularly problematic. If you know all the tricks (things like set -e, set -o pipefail, always capture stderr, etc.), it's not too bad, but there are still so many traps that the mental overhead of catching every single edge case is exhausting.
One nasty issue with Bash is quoting, escaping and word splitting, because almost everything in Bash is about string manipulation. There are so many edge cases: $FOO is not the same as "$FOO", and sometimes you have no choice but to turn it into an array with eval FOO=($FOO) if you need mimic Bash's tokenization behaviour (e.g. respect backslashes and quoting).
(Yes, Bash has arrays! A lot of scripts get simpler when you realize this.)
These days I reach for Ruby very quickly even for small things.
> The moral of the story? Same as always: quote, quote quote. Even when things appear to work.
I would like to propose a different moral of the story, which is, as soon as your shell script requires control flow beyond executing a series of commands one after another, switch to a better programming language, such as Python.
Shell still has a huge advantage over other scripting languages- the syntax to invoke other programs is completely first class and natural. In real programming languages it's always some .os.exec() or whatever. Is python different?
subprocess.check_call and subprocess.check_output are far less error-prone than the first class natural syntax of sh, where you have to remember to quote everything or have hidden bugs, likely even security vulnerabilities. And god forbid your coworker has to try to understand your sh code.
Use the proper tool for the job, that I agree with. For most sysadmin tasks, that is shell scripts. Yes you have to be aware of the warts, as with any language or framework.
I would no more want to do system administration with Python than I would want to write a web application server in bash.
I used to write many Perl tools for command line (I currently use Python
because of team's tribal knowledge), though I make the choice between
Perl/Python and shell looking not at the expected size, but at what kind of
processing it is. Shell is an excellent tool when it comes to call external
commands and generally working with files, and no other language makes it as
easy.
Replacing bash+sed+awk is more or less why Perl was written initially (which also explains much of its syntax). I like it better than shell scripts mostly because the built-in data structures are a bit nicer. And I like Perl/PCRE regex syntax better than POSIX regex syntax.
I went the opposite way. I started out writing few liner bash scripts that just basically wrapped my perl script with proper environment vars, args, etc. and do all the heavy lifting in Perl. It also happens that my dad was a a build engineer back when they didn't really have a title for it and his Swiss Army knife was Perl. I literally learned coding beyond BASIC on HP-UX, Solaris, and Perl.
At some point, I just started doing more and more in bash and started using Perl only specifically for anything I was having trouble getting working with sed+awk.
As of today it's probably been 3+ years since I wrote anything in Perl, I'll just drop to Python if I need it now. Also my sed and awk skills have gotten better.
Bash is such a painful and error-prone language, I'm surprised anyone still writes it accept as a last resort. I've tried to mostly replace bash scripts with Python personally. It'd be excellent to see a bash meta language emerge something like what CoffeeScript or ES6 have done for JavaScript. App-level programmers shouldn't have to deal with this kind of bad "UX" regularly.
Well enough with libraries like Fabric and Click. Though my projects still occasionally have Makefiles with bash one-liners for commands like what one would put in package.json in a Node app. It's almost all upside. The only real catch is if you're running against an unknown system Python version, but using virtual environments solves that and is the best practice anyway even if it feels a little heavy for one file < 100 lines.
I also really like the pattern Django uses for extensible custom commands. I haven't built a system that extensive for my own apps yet but have enjoyed working with it when I am doing Django apps.
This problem isn't a Bash problem, nor is it a shell problem. '[' is a binary that takes a set of args terminates with a ']'. It's often just a symlink to the 'test' binary.
The other "problem" is quoting. In which case what would you expect to happen? Most people avoid spaces in file paths for this very issue. Argument passing is whitespace separated, so an application has no idea that you meant two (or more) args to be just one. Simple solution, use quotes.
If you were to shun all CLI usage in favor of another programming language, then you would be doing the exact same thing with system calls; Using the example from the article for consistency (even though it wouldn't make much sense in a more expressive language):
There's no disagreement there. Let's remember that `test` is older than most programming languages. I can't find an exact date, but 1979 is the earliest mention of it being in `sh v7` and turned into a UNIX System III built-in in 1981. And, that it was created to solve a problem where there is no type system, so there can be no test for NULL
The Bash-ism `[[` (Compound Conditional) is an attempt to reduce the unintuitiveness of `[` or `test`. However, you would still need to quote "$var". The biggest pain point of using an external application like `[` is that `&&` is a shell construct. As you may know, `&&` means "if the previous program succeeds, then execute this next statement. But, if you do `[ -z "$var" && ...`, then `[` will always fail because the command lacks the terminating arg `]`. To do conditionals with `[` you would have to use `-a` for "and" or `-o` for "or". You would want to do either `[ -z "$foo" ] && [ -z "$bar" ]` or `[ -z "$foo" -a "$bar" ]`.
Conditional Expressions relieve those issues as `&&` and `||` will work between the braces. So, `[[ -z "$foo" && -z "$bar" ]]` is okay.
(Some other pain points to `test` are `=` tests for string equality, not assignment; although, `==` is a synonym. For numerical equality testing, there are `-eq`, `-ne`, `-lt`, `-gt`, etc. Then, for those unfamiliar with the Unix way (and even, to some extend, to those who are), there are three cryptic sets of tests for File Type, Access Permission, and File Characteristics.)
But, then, Conditional Expressions makes a few head scratching changes. Like `<` and `>` are for sorting strings; not numbers. And, instead of leaving `-a` and `-o` as-is, they were remapped so that `-a` is synonymous with `-e` (wat?) and that `-o` is for testing shell options. (Doing a little research tell me that `[[` comes from `ksh88`, so maybe they're there for parity.)
the nice thing about standards is that you don't have to make sure the script runs in a billion different shells. You just have to make sure your code is POSIX compatible, and all of a sudden, all POSIX compatible shells will interpret it just fine! Isn't that fantastic?
Isn't that like arguing that one should code only in the form of polyglots that would run on any major programming language? The reality is that bash itself is both highly portable and already widely ported: in 2017 (or 2007) my bash script will run on essentially as many platforms as your POSIX shell script (including the aforementioned HP-UX: the last time I had access to one of those I definitely had access to a copy of bash; there was no reasonable JVM available!! but I definitely had bash).
> And yes, `[[ ... ]]` is a bashism. It's not going work on the HP-UX box, that you do not have in your server fleet.
I wish. I've to take care not to break things like AIX, HP-UX, Solaris 10, Openbsd 5.1. That's an open source project, but there've been actual production customers complaining when we broke things in the past...
EDIT: corrected openbsd version, machine has been updated since the last complaint
I've just tried that out, it seems like it's OK as long as the argument is not exactly `-[abcdefgGhknoprsStuwxz]`, it's ok to have "-d test" or "-dtest".
98 comments
[ 2.7 ms ] story [ 157 ms ] threadIt's a bit like `checkbashisms` but a lot more expansive and with formal classification of the warnings.
Also, every rule in shellcheck has a corresponding github wiki page which describes the issues and common fixes. For example SC2002 (https://github.com/koalaman/shellcheck/wiki/SC2002)
EDIT: "and requires 2GB of RAM to compile." whaaat?
I'm very surprised you've been getting by with less than a gig. What's your arch and GHC version, if you don't mind?
Older GHC versions appeared to use significantly less RAM, and I imagine 32bit pointers would save a lot.
Your guess is right, I do my builds in Debian 8 with ghc and cabal from the repository. I do cabal update before compilation but I don't think this upgrades the ghc so the details should be like:
arch: i686
ghc: 7.6.3
I also use these arguments for cabal install but no idea if they affect anything:
Edit to add: http://stackoverflow.com/questions/669452/is-preferable-over...
> %q ARGUMENT is printed in a format that can be reused as shell
> input, escaping non-printable characters with the proposed POSIX
> $'' syntax.
you can even go further to do "${bar}", this is even safer I feel.
Most of the time, whatever is in [[ ]] can be easily emulated with POSIX/SUS-compatible tools and most of the time it doesn't matter that the tool is not a shell built-in.
Let's take people's favourite =~ operator. In bash, according to documentation, it uses ERE syntax. In zsh, it is ERE or PCRE, depending on an option being set. This is a subtle but important difference in how [[ ]] works. And then again, your environment can change the behaviour of a script you're running, which is (a) unexpected and (b) undesirable, and the change in the behaviour can range from "doesn't find thing it should" to "crashes" to "goes awry in an unexpected way".
At one point in history, options were more limited. Now, it is just not worth the time to read code that may or may not have obscure errors in its very expressions that you must understand before you can even begin to examine the real purpose of the program!
And once you do, shell is really, really useful and convenient.
For example, Linux distributions are full of shell scripts that would be frankly awful to write in any other language that I know of... like this, to take a random example:
https://github.com/NixOS/nixpkgs/blob/master/pkgs/applicatio...
I still disagree with the ideology that any non-trivial script needs to be rewritten in Python. The shell is the standard way of interacting with a *nix system, and for me that's the major benefit of using shell for "system"-ish tasks. I already know how to make a symlink, a pipeline, a chmod, etc, so I don't need to learn a new syntax for that.
The semantics of shell string interpolation are generally not that hard to learn, compared to learning all the nuances of programming in general that can make your program incorrect, so I find it a bit strange how it's considered to make shell completely useless for scripting.
Shell scripting has been working pretty well since, like 1975! It's not that bad!
Which works as long as attacker (or sleepy admin) doesn't happen to create suitably "bad" path
> Shell scripting has been working pretty well since, like 1975! It's not that bad!
Considering the amount of borked system because spaces/uninitialized variables -stories I've heard over years, I'm not so sure about how well it has been working...
This is much nicer than I expected, but the functions are scattered around a bit.
Alternatively there's always the option of using something like sh[0] and just shelling out to the existing utilities, if you don't mind the portability loss.
[0] https://amoffat.github.io/sh/
Not really, I've also forgotten to convert a few dashes to underscores in f-strings and probably made a few other mistakes along the way. It doesn't detract from the main point: it's a rough estimate of how such a converted script would look.
Though it makes me wonder if PyLint already has a lint for strings which start with F and have f-string placeholders.
>> once you do, shell is really, really useful and convenient
Part of the issue here is that a large number of people who think they know how to write safe shell scripts do not in fact have the ability to do so.
There are so many gotchas to learn that it's all too easy to believe oneself competent, while being unaware of just how short one's knowledge falls. It's just too easy to write a script in the shell of your choice that works - until it encounters an edge case scenario that wasn't planned for, at which point the results can be catastrophic. This is true of any programming language, but the tasks for which people write shell scripts (such as massive system-wide filesystem manipulation) makes the consequences of a single-line bug very real.
Glad you mentioned that, because that was going to be my reply after reading the first half of your post.
I'm not sure a person who is fairly well-versed in shell scripting is going to do a worse job than trying to use python, perl, lua, etc. if he doesn't also hsve a high level of experience and compentence in those languages.
It has been literally 10 years since I touched Python. I use shell scripting every day. I know which one I'm going to be better at using to produce reliable code that does what I want it to do.
That's fine. Python 2.6 is still in wide use.
For my day-to-day shell, I use fish. IMO, it has much better defaults than bash and zsh, and it's fast. I still have to use bash sometimes, but it's becoming increasingly rare.
Your linked example doesn't really seem particularly tricky to me. I'd feel comfortable writing it in something like node or ruby, and I believe the result would probably be easier to consume for certain people.
I just don't think we should reject shell scripts entirely because some care is required to write them correctly. Shell is way too valuable.
I don't reject shell scripts, though. I agree that in some cases it can lead to much simpler code. The power and composability with pipes and immediate substitutions is incredibly powerful. I've had a few cases where I replaced far more complicated node scripts with simpler shell scripts.
What I'd truly love is a reference guide that showed how to correctly do certain things with shell scripts. Here's a real-world example I struggled with: Writing end-to-end tests with selenium and a test runner. I wanted a script to start up a selenium server and wait for it to be ready. Then it should start the test runner. If the test runner exited successfully, kill the selenium server and browser, and exit with a 0. If the test runner or selenium server crashed or failed somehow, clean up and exit with a 1. Even after much trial and error, I still ended up with occasional Chrome copies staying open after failures.
That kind of reference guide would be great. I haven't checked out any of the books on shell programming, but there should be a decent audience for a website with just good patterns for shell scripts, like you describe.
Trapping signals and keeping track of child PIDs is quite tricky, yeah. (It's quite tricky in any language!)
You might be interested in my "wd" program that's basically a bash wrapper for the WebDriver API, which I made because I wanted to write end-to-end tests and automation tools in shell.
https://github.com/mbrock/wd
I also will never forget the time I spent a day doing a perl script to parse some data... took some time, came back, and did it in a one line awk... Very powerful.
I think somewhere in here is room for discussion of data, text, and the unix philosophy as everything as a file.
Most sysadmins do not use python routinely. They work in the shell all day every day though.
I would argue that a well written Python script is going to be more comprehensible to the average sysadmin than an equivalent shell script, even assuming the sysadmin is more conversant in shell. More portable and testable (!!) too.
[0] http://www.catb.org/esr/writings/unix-koans/ten-thousand.htm...
Error handling is particularly problematic. If you know all the tricks (things like set -e, set -o pipefail, always capture stderr, etc.), it's not too bad, but there are still so many traps that the mental overhead of catching every single edge case is exhausting.
One nasty issue with Bash is quoting, escaping and word splitting, because almost everything in Bash is about string manipulation. There are so many edge cases: $FOO is not the same as "$FOO", and sometimes you have no choice but to turn it into an array with eval FOO=($FOO) if you need mimic Bash's tokenization behaviour (e.g. respect backslashes and quoting).
(Yes, Bash has arrays! A lot of scripts get simpler when you realize this.)
These days I reach for Ruby very quickly even for small things.
I would like to propose a different moral of the story, which is, as soon as your shell script requires control flow beyond executing a series of commands one after another, switch to a better programming language, such as Python.
Clear syntax is better than pretty syntax.
I would no more want to do system administration with Python than I would want to write a web application server in bash.
At some point, I just started doing more and more in bash and started using Perl only specifically for anything I was having trouble getting working with sed+awk.
As of today it's probably been 3+ years since I wrote anything in Perl, I'll just drop to Python if I need it now. Also my sed and awk skills have gotten better.
Context: I have the same feelings about Bash.
I also really like the pattern Django uses for extensible custom commands. I haven't built a system that extensive for my own apps yet but have enjoyed working with it when I am doing Django apps.
https://docs.djangoproject.com/en/1.11/howto/custom-manageme...
The other "problem" is quoting. In which case what would you expect to happen? Most people avoid spaces in file paths for this very issue. Argument passing is whitespace separated, so an application has no idea that you meant two (or more) args to be just one. Simple solution, use quotes.
If you were to shun all CLI usage in favor of another programming language, then you would be doing the exact same thing with system calls; Using the example from the article for consistency (even though it wouldn't make much sense in a more expressive language):
That is a lot more readable to me than:
The Bash-ism `[[` (Compound Conditional) is an attempt to reduce the unintuitiveness of `[` or `test`. However, you would still need to quote "$var". The biggest pain point of using an external application like `[` is that `&&` is a shell construct. As you may know, `&&` means "if the previous program succeeds, then execute this next statement. But, if you do `[ -z "$var" && ...`, then `[` will always fail because the command lacks the terminating arg `]`. To do conditionals with `[` you would have to use `-a` for "and" or `-o` for "or". You would want to do either `[ -z "$foo" ] && [ -z "$bar" ]` or `[ -z "$foo" -a "$bar" ]`.
Conditional Expressions relieve those issues as `&&` and `||` will work between the braces. So, `[[ -z "$foo" && -z "$bar" ]]` is okay.
(Some other pain points to `test` are `=` tests for string equality, not assignment; although, `==` is a synonym. For numerical equality testing, there are `-eq`, `-ne`, `-lt`, `-gt`, etc. Then, for those unfamiliar with the Unix way (and even, to some extend, to those who are), there are three cryptic sets of tests for File Type, Access Permission, and File Characteristics.)
But, then, Conditional Expressions makes a few head scratching changes. Like `<` and `>` are for sorting strings; not numbers. And, instead of leaving `-a` and `-o` as-is, they were remapped so that `-a` is synonymous with `-e` (wat?) and that `-o` is for testing shell options. (Doing a little research tell me that `[[` comes from `ksh88`, so maybe they're there for parity.)
Manpage for `test`, `[`: https://www.mankier.com/1/test
Conditional Expressions: http://www.gnu.org/software/bash/manual/html_node/Bash-Condi...
And yes, `[[ ... ]]` is a bashism. It's not going work on the HP-UX box, that you do not have in your server fleet.
There are also a relatively large number of tcsh users in the world. This is why, even now on latest fedora, /etc/profile.d contains csh scripts.
As for sh, standards exist for a reason. Try to empathize with people who are not in your bubble.
That means dash & ash.
Portability can matter.
I wish. I've to take care not to break things like AIX, HP-UX, Solaris 10, Openbsd 5.1. That's an open source project, but there've been actual production customers complaining when we broke things in the past...
EDIT: corrected openbsd version, machine has been updated since the last complaint
In Bourne once scripts I have been testing
In the past I have seen others useI always just use [ "$var" ] and it's never failed me so far; should I be worried?
As an aside/self-plug, I wrote a blog post[0] complaining about my least-favorite Bash gotchas.
0. https://lambda.complex.rocks/2017/03/why-i-dont-like-bash/