
[ -z $var ] works unreasonably well - ingve
http://www.vidarholen.net/contents/blog/?p=576
======
makecheck
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!

~~~
mbrock
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:

[https://github.com/NixOS/nixpkgs/blob/master/pkgs/applicatio...](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/virtualization/docker/default.nix#L99)

~~~
masklinn
Eh. Looks OK to me, even with Python probably not being the tersest shell
language:
[https://gist.github.com/anonymous/4203b7c6ffc9cb9ecf34d17cb8...](https://gist.github.com/anonymous/4203b7c6ffc9cb9ecf34d17cb88ecb5e)

~~~
Too
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.

~~~
mbrock
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!

~~~
zokier
> 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...

~~~
mbrock
Attackers who have commit access to your repository can do a lot of bad things
quite easily.

------
frou_dh
FYI the author also offers an absurdly good commandline shellscript linter:
[https://github.com/koalaman/shellcheck](https://github.com/koalaman/shellcheck)

It's a bit like `checkbashisms` but a lot more expansive and with formal
classification of the warnings.

~~~
philipov
Woa, it's written in Haskell. Awesome!

EDIT: "and requires 2GB of RAM to compile." whaaat?

~~~
jessaustin
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.

~~~
yjftsjthsd-h
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 :)

------
AndyKelley
> 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.

~~~
woah
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?

~~~
fnj
Xonsh (a terrific python-based shell) addresses this with great elegance.

------
munchhausen
Please. Use `[[ -z $var ]]` instead, and get this load of nonsense off your
table.

And yes, `[[ ... ]]` is a bashism. It's not going work on the HP-UX box, that
you do _not_ have in your server fleet.

~~~
fnj
It won't work on dash or on BSD ash either. Do what you want, but you won't be
doing it working for me because I care about portability.

~~~
pyre
Better make sure those shell scripts all run in csh/tcsh too. Never know when
you'll be running on MacOS 10.1!

~~~
mort96
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?

~~~
comex
csh and tcsh are not POSIX compatible.

------
cryptarch
How is:

    
    
        [ -z $var ]
    

different from:

    
    
        [ $var ]
    

? I don't understand the distinction between a length-0 string and "the null
string" in the context of /bin/sh.

I always just use [ "$var" ] and it's never failed me so far; should I be
worried?

~~~
AgentME
If var starts with a '-' then you'll get surprises.

~~~
cryptarch
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".

------
tedmiston
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.

~~~
yes_or_gnome
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):

    
    
        var = "some value";
        exec("[", "-z", var, "]");

~~~
tedmiston
I was referring to replacing the logic with Python, ie:

    
    
        if var is not None:
            ...
        else:
            ...
    

Or

    
    
        if not var:
            ...
    

Or

    
    
        if var == '':
            ...
    

Etc. Depending on what you want to achieve.

That is a lot more readable to me than:

    
    
        [ -z $var ]

~~~
yes_or_gnome
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.)

Manpage for `test`, `[`:
[https://www.mankier.com/1/test](https://www.mankier.com/1/test)

Conditional Expressions:
[http://www.gnu.org/software/bash/manual/html_node/Bash-
Condi...](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-
Expressions.html)

------
waynecochran
Is anyone like me and just uses Perl as a Bash replacement? If I need to write
more than a 2 line script, I just use Perl.

~~~
_delirium
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.

------
jamroom
while not as portable, using double brackets - i.e.

    
    
      [[ -z $var ]]
    

can be a bit "safer" in regards to quoted variables. Works in bash and KSH.

Edit to add: [http://stackoverflow.com/questions/669452/is-preferable-
over...](http://stackoverflow.com/questions/669452/is-preferable-over-in-bash-
scripts)

~~~
dozzie
Don't use that. If anything, use properly quoted variables:

    
    
      [ -z "$var" ]
    

This has literally no possibility of failing in any weird way, no matter which
Bourne or Korn shell descendant you use.

~~~
ausjke
in nearly all cases, you should always do "$var" instead of $var, this should
be mandated on sh scripts.

you can even go further to do "${bar}", this is even safer I feel.

~~~
scurvy
Yes, it's "${bar}" that should go into style guides and linters (for
consistency).

~~~
brazzledazzle
It seems kind of petty I guess but that seems kind of annoying to write and
even less importantly looks pretty blah.

------
gwu78
Can an "empty" variable have a length greater than zero?

In Bourne once scripts I have been testing

    
    
       test ${#var} -gt 0 and
    
       test ${#var} -ge 1
    

In the past I have seen others use

    
    
       test x"" = x$var

~~~
feld
Yes, the test to see if "x" matches is the old school trick I was taught

------
LambdaComplex
Interesting, but not all that surprising when you stop to think about it: give
it invalid arguments and it fails, with a corresponding exit code.

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/](https://lambda.complex.rocks/2017/03/why-i-dont-like-bash/)

------
jstanley
What about if $var is '== -z' :)

~~~
raphlinus
Confirmed. The title statement is wrong. I think the moral of _that_ story is
that not even renowned experts can reliably get bash right.

------
IshKebab
Yeah, in that it vaguely works in some cases.

------
belovedeagle
Better lesson? Use zsh, where variable​s don't need to be quoted because
they're expanded as a single shell word.

~~~
yjftsjthsd-h
Thereby destroying compatibility with sh. At that point, why not use Python?

