12

This works on a shell (bash, dash) prompt:

[ -z "" ] && echo A || echo B
A

However, I am trying to write a POSIX shell script, it starts like this:

#!/bin/sh

[ "${#}" -eq 1 ] || echo "Invalid number of arguments, expected one."; exit 1

readonly raw_input_string=${1}

[ -z "${raw_input_string}" ] && echo "The given argument is empty."; exit 1

And I don't know why, but I don't get the message:

The given argument is empty.

if I call the script like this:

./test_empty_argument ""

Why is that?

  • 5
    See How can I test if a variable is empty or contains only spaces? for ways on testing if a variable is empty, unset, or only contains blanks. The issue in this question has nothing to do with that. – ilkkachu Apr 3 at 17:12
  • 1
    Just use if [ X”” = X”$var” ] ; then echo isempty ; fi – user2497 Apr 3 at 17:37
  • 3
    @user2497 There is no reason to use that in any shell released in the last 20 years. That's a workaround for old, buggy shells. – chepner Apr 3 at 18:11
  • @chepner So it is not a valid solution? Something else must be used? – user2497 Apr 3 at 18:31
  • 6
    [ "" = "$var" ] would work fine; a quoted empty string will not be removed from the argument list of [. But that's not necessary either, because [ -z "$var" ] also works just fine. – chepner Apr 3 at 18:50
37

Note that your line

[ "${#}" -eq 1 ] || echo "Invalid number of arguments, expected one."; exit 1

this is the same as

[ "${#}" -eq 1 ] || echo "Invalid number of arguments, expected one."
exit 1

(an unquoted ; can, in most circumstances, be replaced by a newline character)

This means that the exit 1 statement is always executed regardless of how many arguments were passed to the script. This in turn means that the message The given argument is empty. would never have a chance of getting printed.

To execute more than a single statement after a test using the "short-circuit syntax", group the statements in { ...; }. The alternative is to use a proper if statement (which, IMHO, looks cleaner in a script):

if [ "$#" -ne 1 ]; then
    echo 'Invalid number of arguments, expected one.' >&2
    exit 1
fi

You have the same issue with your second test.


Regarding

[ -z "" ] && echo A || echo B

This would work for the given example, but the generic

some-test && command1 || command2

would not be the same as

if some-test; then
    command1
else
    command2
fi

Instead, it is more like

if ! { some-test && command1; }; then
    command2
fi

or

if some-test && command1; then
    :
else
    command2
fi

That is, if either the test or the first command fails, the second command executes, which means it has the potential to execute all three involved statements.

18

This:

[ "${#}" -eq 1 ] || echo "Invalid number of arguments, expected one."; exit 1

is not:

[ "${#}" -eq 1 ] || { echo "Invalid number of arguments, expected one."; exit 1; }

But instead is:

{ [ "${#}" -eq 1 ] || echo "Invalid number of arguments, expected one."; } 
exit 1

Your script is exiting regardless of how many arguments you passed to it.

8

One way to make it more readable is to define a die function (à la perl) like:

die() {
  printf >&2 '%s\n' "$@"
  exit 1
}

# then:

[ "$#" -eq 1 ] || die "Expected one argument, got $#"

[ -n "$1" ] || die "Empty argument not supported"

You can add more bells and whistles like colours, prefix, line number... if need be.

  • In practice, do you ever call your die function with multiple arguments? (If so, can you give an example?) I use an almost identical die function, but use "$*" instead, which may be more what you're intending? – jrw32982 Apr 3 at 18:15
  • 3
    The value of "$@" is that it allows multi-line messages without needing to add literal newlines. – Charles Duffy Apr 3 at 20:54
  • 1
    @jrw32982, using "$*" to join args with spaces also means you need to set $IFS to SPC for it to work in all contexts including those where $IFS has been modified. Alternatively with ksh/zsh, you can use print -r -- "$@" or echo -E - "$@" in zsh. – Stéphane Chazelas Apr 4 at 7:17
  • @CharlesDuffy Yes, but I've never seen that done in the context of a die-type function. What I'm asking is: in practice, have you ever seen anyone write die "unable to blah:" "some error", for the purpose of getting a 2-line error message? – jrw32982 Apr 4 at 13:24
  • @StéphaneChazelas Good point. So (in my formulation) it should be die() { IFS=" "; printf >&2 "%s\n" "$*"; exit 1; }. Have you ever personally used this kind of die function to have printf generate a multi-line error message by passing multiple arguments? Or do you only ever pass a single argument to die so that it only adds a single newline to its output? – jrw32982 Apr 4 at 13:29
-1

I've often seen this as a test for an empty string:

if [ "x$foo" = "x" ]; then ...
  • Should have been "=" - fixed. – wef Apr 3 at 6:56
  • 3
    That practice is literally from the 1970s. There is no reason whatsoever to use it with any shell that is compliant with the 1992 POSIX sh standard (so long as correct quoting is used and now-obsolescent functionality such as -a, -o, ( and ) as derectives to tell test to combine multiple operations in a single invocation are avoided; see the OB markers in pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html). – Charles Duffy Apr 3 at 20:52
  • Non-linux unices shipped with the original 'sh' well into the '90's - maybe still do. In my job at that time, I had to write portable install scripts and I used this construct. I just looked at the install scripts that NVidia ships for linux and they still use this construct. – wef Apr 3 at 22:21
  • NVidia may use it, but that's not to say they have any technical justification to do so; cargo-cult development in commercial UNIX is sadly prevalent. Even Heirloom Bourne doesn't have the bug in question -- so the SunOS codebase (that being the last commercial UNIX to ship a non-POSIX /bin/sh, and Heirloom Bourne's immediate predecessor) didn't have it either. – Charles Duffy Apr 3 at 23:13
  • 1
    I don't wear a hat, so I can't promise to post a YouTube video eating it should someone turn up a shell published on a commercial UNIX with this bug post-1990... but if I did, it would be tempting. :) – Charles Duffy Apr 3 at 23:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.