Readit News logoReadit News
jmmv · 2 years ago
Hey, original author here. Thanks for sharing this and making it rise to the front page! :) By the way, the title probably deserves a (2020) and it would be nice if "test" wasn't capitalized, because it actually refers to the command.

Here is something related from 2021 that also touches on bash's [[ operator and that I think you might enjoy in this context: https://jmmv.dev/2021/08/useless-use-of-gnu.html

o11c · 2 years ago
[[ is not really a builtin, it's fundamentally syntactical (but presumably uses a mostly-inaccessible builtin internally). Fun fact, `]]` is also a reserved word despite never being allowed in a context where reserved words matter.

In some non-bash shells, the `function` keyword is needed to declare certain types of function.

For make `$(shell)`, if you're building a lot of targets the performance difference can be measurable. Still, it loses in the nop case, so you should actually usually do `include` to trigger re-making.

GNU is completely right to ignore POSIX, since POSIX is not useful for solving most real problems.

pmarreck · 2 years ago
> since POSIX is not useful for solving most real problems

POSIX, being a standard that can be tested, is at least a specification or agreement that can be met by multiple products to enable them to interact

account42 · 2 years ago
> In some non-bash shells, the `function` keyword is needed to declare certain types of function.

In shells that aren't POSIX-compliant [0], maybe. In which case: yes, there are many wildly different scripting languages with REPLs.

[0] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...

account42 · 2 years ago
A lot of the GNUisms described in https://jmmv.dev/2021/08/useless-use-of-gnu.html are very useful in interactive use. Search the current directory without an explcit . - useful. Append an option to a command you just typed - heck yeah, always annoyed when commands don't support that.

For scripts though, sticking to POSIX sh often makes sense, yeah. You should at least be aware if you use of Bash-isms.

gnfargbl · 2 years ago
This article complains that using the extended GNU features (--ignore-case, set -o pipefail etc) makes scripts less portable. Fair enough.

What it doesn't explain is why a Linux user should much care about portability. OpenBSD and FreeBSD are alive and well, but the number of users seems so small that they aren't a particular concern. Maybe you could argue that we "should" consider these OSes out of a sense of fairness, but where does that stop? Do I also need to consider something obscure like vxWorks?

BusyBox (Alpine) is more interesting, but the changes there are so significant that a port will almost always be needed anyway.

Are there other compelling reasons to care about the non-GNU ecosystem?

sundarurfriend · 2 years ago
I find the obsession with shell-script portability in contexts where it doesn't matter to be bizarre, but this particular argument is amusing:

> the number of users seems so small that they aren't a particular concern. Maybe you could argue that we "should" consider these OSes out of a sense of fairness, but where does that stop?

This is the same argument that many companies and products have made over the years (and still do at times) for ignoring Linux. To have a Linux user use that same argument against OSes with even smaller userbases is kind of amusing to see.

kqr · 2 years ago
Is your comment best read in Internet Explorer 6?

----

What I'm trying to say is that open standards (and the portability that comes with them) is not something that just happens on its own. It takes active maintenance, and part of that maintenance is opting to adhere to the standard even when it would be more convenient to use extensions available in the most popular systems.

Will you personally suffer from liberally using Bashisms? Not in the first order. But if we encourage that sort of thinking as a rule, the standards become meaningless. I believe that would be a net negative change for the world, but there are many intelligent people who would disagree.

chasil · 2 years ago
You will notice that the parent article mentions "dash."

The dash shell is small and fast, but it does not allow any bash/korn language extensions beyond what was recorded in the POSIX.2 standard in the early 1990s.

Linux users should care because the Debian/Ubuntu family use dash as the system shell, so this problem is very real as many have learned.

pjungwir · 2 years ago
I want to agree, but macOS is a big one. For example `sed -i` has bitten me before.
jonhohle · 2 years ago
`-o pipefail` isn’t a GNU extension or even a difference in user space programs, but specifically a bash option. The portability is between shells, not operating systems. It will work on bash, regardless of the underlying OS, but may or may not work on csh, ksh, fish, etc.
jmopp · 2 years ago
Mac's command-line tools are the BSD versions. I always get confused about the command-line arguments for `date` because they differ so much betwen platforms.
hwc · 2 years ago

    if [ a = b ] || grep -q ^hello$ /usr/share/dict/words; then
      echo "test failed and grep succeeded"
    fi
> “You pick whether to be amused or horrified. I don’t know how exactly my coworker reacted when I hinted at this during a recent code review I did for them.”

Isn't that normal everyday shell use?

OJFord · 2 years ago
The only thing that might 'horrify' me in code review for that is the assumption that it only evaluates true when test failed.

(And I might quote '^hello$' personally just on principle for having special chars, especially dollar, and to help syntax highlighters.)

a1369209993 · 2 years ago
> https://jmmv.dev/2021/08/useless-use-of-gnu.html

Tangential, but your blog software seems to have mangled the headings; it's serving eg:

  make $(shell …) expansion
instead of

  make $(shell ...) expansion
(note that (as is correctly written in the body) that's three pediods, not one elipsis, so mldr wouldn't be correct even on it's own, meaning there's two probably-unrelated bugs affecting it).

jmmv · 2 years ago
Hmm… I’ll have to check later but right now I’m seeing the right thing in iOS. The blog is built by Hugo, so it’s all static files. But maybe something changed with the latest update. Thanks.
dang · 2 years ago
Ok, I've lowercased the leading 't'. We never do that but for this, ok :)
edanm · 2 years ago
As per the Zen of Python:

> Special cases aren't special enough to break the rules.

> Although practicality beats purity.

:)

Deleted Comment

kqr · 2 years ago
Taking the last point one step further, we can also dispense with the if block entirely:

    if [ a = b ]; then
        echo "Oops!"
    else
        echo "Expected; phew!"
    fi
becomes

    [ a = b ] && echo "Oops!" || echo "Expected; phew!"
I'm not sure how often you should do this but sometimes it comes in handy for things like

    [ "$debug" ] && echo "what's going on" >&2
to conditionally print debug output to stderr.

----

And the fact that the if block tests a regular command means we can also do things like

    if grep -q 'debug' /var/log/nginx/access.log; then
        echo "Debug request found!"
    fi
----

Something I have not yet bothered to figure out is whether I should write

    [ $(expr 1 + 1) -eq 2 ] && [ $(expr 2 + 2) -eq 3 ]
or use the built in logical and of test:

    [ $(expr 1 + 1) -eq 2 -a $(expr 2 + 2) -eq 4 ]
As long as performance is not a concern, I can see roughly equal reasons in favour of either.

illo · 2 years ago
> [ a = b ] && echo "Oops!" || echo "Expected; phew!"

Not to be taken as a general rule though. I might be mistaken but I think that bash would parse the line as:

  ([ a = b ] && echo "Oops!") || echo "Expected; phew!"
so if the command sequence after `&&` fails, then the code sequence after `||` is executed anyway:

  illo@joe:~ $ [ "a" == "a" ] && >/dev/full echo "strings match" || echo "strings don't match"
  -bash: echo: write error: No space left on device
  strings don't match
  illo@joe:~ $
This is different from the semantics of the `if` block:

  illo@joe:~ $ if [ "a" == "a" ]; then >/dev/full echo "strings match"; else echo "strings don't match"; fi
  -bash: echo: write error: No space left on device
  illo@joe:~ $

cesnja · 2 years ago
I'd advise against that kind of shortening. If you use set -e, which you should, then

    if [ a = b ]; then
      echo "Oops!"
    fi
will do exactly what you imagined, but

    [ a = b ] && echo "Oops!"
will quit with an error if expression a does not equal expression b.

Calzifer · 2 years ago
No it won't. set -e is implicit disabled for the first command with && and ||. Same for a command after if/while/until and after !. It should only matter if you implicit return immediately after.

  $ bash -ec 'if [ 1 = 2 ]; then echo true; fi; echo $?'
  0
  $ bash -ec '[ 1 = 2 ] && echo true; echo $?'
  1
In both cases it does not quite and execute the last echo.

nwellnhof · 2 years ago
> Something I have not yet bothered to figure out

According to POSIX, the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t... under "Application Usage".

Deleted Comment

darrenf · 2 years ago
Regardless of `-a` vs two tests and `&&`, there's no need to shell out to `expr` if bash's arithmetic evaluation is available:

    [ $((1+1)) -eq 2 ]

neuromanser · 2 years ago
arithmetic evaluation is ((…)), $((…)) is arithmetic expansion.

There’s no need for test(1) / [(1) or conditional expressions ([[…]]) if you’re doing arithmetic:

if ((1+1 == 2)); then …; fi

kqr · 2 years ago
I'm going to assume that if one is using [ rather than [[ then one will also want to use expr rather than $(()).
cellularmitosis · 2 years ago
I stopped using [ a few years ago because ‘test’ reinforces the idea that this is just a command like any other, not syntax. Also, “man test” is much more pleasant that sifting through “man bash”.
kazinator · 2 years ago
Your comment makes no sense. GNU Coreutils has a "man [" as well as "man test" man page.

Bash has "help test" for a quick cheatsheet.

The [ command is very old; it was already present in Version 7 Unix in 1979.

Karellen · 2 years ago
I think GP's point was that `[` feels like syntax, but - importantly - isn't.

Yes, `[` is a command, and has a man page and everything, but in a script it doesn't look like a command. It looks like something "special" is going on.

Whereas using `test` emphasises the point that you're just running another program. That all `if` does is check the output status of whatever program is being run, whether that's `test`/`[`, or `grep`, or anything else at all.

(Personally, I don't think that emphasis is necessary. But I've been using shell scripts long enough that I understand these nuances fairly well without having to think about them much any more. So I think that GP's point is a reasonable perspective to have, and worth considering.)

c0l0 · 2 years ago
Parts of the comment make a LOT of sense actually, when you look at shell scripts written by the uninitated. Often times, I see constructs like

    while [ 1 ]; do ...; done
which are a pretty clear indication of the author's misconception about the perceived nature of [ ], I think.

rascul · 2 years ago
Note that bash implements [ as a builtin so the coreutils man page might not match exactly.
tuyiown · 2 years ago
I'm with you, the [ (and [[ bashism) introduces lots of confusions about what is really happening, I could never manage any real confidence.

That said, [[ being guaranteed to be built-in certainly had its purpose at ages where shell script performance had any kind of relevance, and that was no so long ago.

kqr · 2 years ago
[[ has more to do with trying to build an intuitive shell scripting environment than performance. [[ makes conditionals behave much more like you'd expect from other programming languages. I think it's a great idea, but then again, if I don't have to care for POSIX portability, I'd rather use something that's not a shell language for scripting.
paiute · 2 years ago
I’ll probably just use test after reading this, I write bash scripts infrequently enough that if statements always get me (whitespace). Now that i see the why it’s super obvious, and using test helps communicate that it’s just args
ridiculous_fish · 2 years ago
The biggest footgun in `[` and `test` is the single argument behavior. For example, you might attempt to check if a variable is nonempty like so:

     [ -n $FOO ]
but if FOO is unset, it expands to nothing (as opposed to the empty string), so this is equvalent to:

     [ -n ]
and POSIX requires that the one-argument form of `[` succeed if that argument (here, "-n") is non-empty. So this will falsely report that $FOO is non-empty.

Remember to quote your variables!

jstimpfle · 2 years ago
I think your last sentence needs to go first. Quote your variables! There's no actual footgun in the specification of the test builtin -- the footgun is shell itself. The behaviour that you mention makes sense because

   [ "$FOO" ]
is always the non-empty check regardless what it contains (could be "-n").

whateveracct · 2 years ago
ShellCheck your scripts!
mr_toad · 2 years ago
Or use set -u to fail early.

Or use [ -n ${FOO-} ] which will replace the unset variable with an empty string.

gpvos · 2 years ago
Also, if $FOO contains a space, it will expand into multiple arguments. Just quote your variables, always.
dixie_land · 2 years ago
[ x"$FOO" != x"" ]
stouset · 2 years ago
Please stop with tricks like this and just quote your variables. Everywhere.
overtomanu · 2 years ago
whoa, lot of history in this trick

https://news.ycombinator.com/item?id=26776956

andrewshadura · 2 years ago
No. This hasn't been necessary for decades.
mattrighetti · 2 years ago
In this case I would use [ -n "${FOO?}" ] so that the script will immediately stop if $FOO is null or unset
8n4vidtmkvmk · 2 years ago
Set -xufo pipefail

Or whatever the magic string is. Enable all the errors at the start of the script

Deleted Comment

JNRowe · 2 years ago
chubot has written an interesting document¹ exploring more of the nuance with test/[/[[, and many of the other entries in that blog have intriguing explanations of the oddities of our shells(a random example²).

¹ https://www.oilshell.org/blog/2017/08/31.html

² https://www.oilshell.org/blog/2016/11/18.html

wodenokoto · 2 years ago
I had no idea [ was a program and the fact that it checks if the last argument is a closing bracket is kinda funny to me.

But at least it explains why you need spaces on both sides of the brackets.

neverrroot · 2 years ago
[[ is bash only. If you know you’ll only use bash, use it. For details, the article is nice.
re · 2 years ago
dredmorbius · 2 years ago
"bash only" typically refers to "bashisms", that is, bash features not present in the plain Bourne shell (or Bourne-compatible interpreters such as dash). The fact that other shells (such as zsh) may include those features ... is beside the point of writing universally compatible shell scripts.

Confirming my facts for this comment, #TIL that "dash" is the "Debian Alquist Shell", that is, Debian's "ash" shell:

<https://en.wikibooks.org/wiki/Guide_to_Unix/Explanations/Cho...>

bgm1975 · 2 years ago
And ksh (ksh88 & later)
emmelaich · 2 years ago
Always use [[

zsh and ksh have it; in fact I'm pretty sure it originated with ksh in 1988 or earlier.

OmarAssadi · 2 years ago
> Always use [[

While zsh, bash, mksh, ksh93, probably others have it, sure. But many don't -- and not totally irrelevant ones either. Debian's default, dash, for example, does not support `[[`.

IMO, unless you're writing something like shell-specific dotfiles, avoid non-POSIX features.

It's usually pretty trivial to avoid them, especially if you're willing to call other mandated commands like awk, etc. But often, with a bit of creative thinking, most non-standard features can be replicated with some combination of `set`, separate functions and/or subshells.

Shell scripts, in general, have dozens of footguns, are pretty much impossible to statically analyze, difficult to make truly robust, and many of the shells themselves -- e.g., bash -- have huge, borderline inauditable codebases.

I can think of a dozen reasons not to write shell scripts. Yet still, there is incredible value in the fact that some form of POSIX-compliant/compliant-enough shell can usually be found on most systems.

All of that value goes out the window, though, the moment you start relying on non-standard features.

nerdponx · 2 years ago
That is, test and [ are specified by POSIX and usually are physical binaries (but might also be masked by shell builtins). Whereas [[ is not specified by POSIX and usually only exists as a shell builtin.
shmerl · 2 years ago
Why would you not use Bash (unless you are using completely different shell like Fish explicitly?).

It feels like some completely archaic concern to target the minimal common shell denominator.

mikem170 · 2 years ago
Bash seems to be about as archaic as sh.

I write my shell scripts in sh, because it comes with every unix-like os by default.

I know that bash has more features, but I've never really missed them. I switch from sh to perl for more complicated tasks.

To each their own, right?

LambdaComplex · 2 years ago
Not installed by default on BSDs. Sometimes not installed in minimal environments, e.g. where BusyBox is all you have available. Maybe not installed by default on Unixes? Not sure
dredmorbius · 2 years ago
Writing Bourne-compliant scripts ensures maximum portability.

As many here have noted, bash isn't universally available, with another possible issue being OpenWRT devices. Stock/base images tend to use a Bourne-compatible shell, not full Bash. Though the latter's installable through opkg, for sufficiently small devices (typical of consumer kit), you simply won't have the space to install them.

There's also the slight PITA that Apple's OSX ships with a very old, pre-GPLv2 Bash, out of licensing concerns. (Apple is phenomenally averse to GPL-based code, much as some *BSDs are, such as OpenBSD.)

And if you're dealing with legacy systems (which tend to be extraordinarily and stubbornly persistently legacy), you'll often find that either bash isn't present or is quite dated.

I freely confess that I tend to write fairly recent-feature bash scripts myself by default, and appreciate many of the newer features. But if and when I am writing portable code, I'll go back to Bourne-compatibility.

But when writing system level code, an appreciation for standards and the very long tail of legacy standards and the limitations they impose is in fact a large component of professional maturity.

JohnFen · 2 years ago
I've worked on several modern projects that needed to be able to run on a wide variety of Unix platforms, several of which didn't have bash. Writing for the common shell denominator was important, not archaic.
bregma · 2 years ago
It's archaic if you're a dabbler, a hobbyist, an academic, or a junior dev who lacks the experience of shipping software into the wild.

For the rest of us, we have learned the hard way, sometime repeatedly, portability and adherence to published standards matters.

yjftsjthsd-h · 2 years ago
IIRC Debian uses dash as /bin/sh because it's faster (execution speed) than bash.
csydas · 2 years ago
it’s contextual as most things. need to actually use and work on the machine? use whatever shell you want to make your life easier.

need a script to run on many different systems and/or need to write a script to be managed automatically by a service account? probably you want a shell with syntax that is guaranteed to be the same on all your systems.

driggs · 2 years ago
Because Apple switched the default macOS shell from the GNU-licensed `bash` to the BSD-licensed `zsh`?
aidenn0 · 2 years ago
IIRC, /bin/sh on Ubuntu defaults to dash
user261 · 2 years ago
I really didn't understand why the last if statement is confusing. Is it because when starting out with shell scripting one would usually assume that the [ is a part of the bash scripting language not just another program? If it's then I think I get it now. Otherwise please mention why it's surprising. Also, @author thanks for a nice article. was a good reed.
computerfriend · 2 years ago
Even if you don't know about [ as a binary, I still don't see how it's confusing. Seems like very normal bash to me.