0

I'm checking the following code:

#!/usr/bin/env bash

set -o errexit
set -o nounset

if [ -n "$@" ]; then
    echo "not null";
else
    echo "null";
fi

This means that if I call my script like so ./my-script.sh parameter it should result with not null and when called like so ./my-script.sh it should say null.

Documentation says that -z means string is null, that is, has zero length and -n means that string is not null.

For some reason my code always claims that $@ is not null.

Why is that?

When I replace -n with -z and swap the content like so:

if [ -z "$@" ]; then
    echo "null";
else
    echo "not null";
fi

then it works correctly.

lewiatan
  • 1,126
  • 2
  • 21
  • 37
  • See also [bash `test` behaves unconformly](https://stackoverflow.com/questions/7890017/bash-test-behaves-unconformly) – Charles Duffy Feb 05 '19 at 16:32
  • BTW, using `errexit` is not universally considered a Good Idea. Consider going through [the exercises in BashFAQ #105](http://mywiki.wooledge.org/BashFAQ/105#Exercises) to grok some of the issues it can cause, or reviewing the list of incompatibilities between different implementations at https://www.in-ulm.de/~mascheck/various/set-e/. – Charles Duffy Feb 05 '19 at 17:30

1 Answers1

3

"$@" isn't a single string: It expands to a variable number of items. If, say, you have ./yourscript one two three, then [ -n "$@" ] becomes [ -n one two three ], which isn't valid syntax. Or if you have zero arguments, it becomes [ -n ], which is true. ([ -z ] is also true, because [ anything ] checks whether anything is an empty string, and "-z" is a non-empty string, just as "-n" is).

If you want to know if "$@" is empty, check $# instead, which will tell you how many items it contains.

if [ "$#" -eq 0 ]; then
  echo "null"
else
  echo "not null"
fi
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Does this mean that `@` variable cannot be tested with `-n`? When I test `[ -n "$@" ]` it always evaluate to true (in both cases- with and without extra parameters), but when I test `[ -n "" ]` it always evaluate to false. Shouldn't first case be evaluated depending on the content of `@`? – lewiatan Feb 05 '19 at 16:33
  • @lewiatan, you weren't testing enough cases. It's certainly not always true; for example, `set -- one two; [ -n "$@" ]` returns false (and logs an error), because `[ -n one two ]` is invalid test syntax. As for `[ -n "$@" ]` in the empty case, that expands to *no* parameters, not to a single empty parameter. Which is to say, it's `[ -n ]`, not `[ -n "" ]`. – Charles Duffy Feb 05 '19 at 16:35
  • 1
    `"$@"` is a list, not a single value, so no parameters aren't equivalent to `[ -n "" ]`, but to `[ -n ]`. – choroba Feb 05 '19 at 16:35
  • 1
    @lewiatan, consider running `set -x` to enable trace-level logging from your shell before experimenting here (or `bash -x yourscript`). That way you can see when something you run is equivalent to `[ -n ]` and when it's equivalent to `[ -n "" ]`. – Charles Duffy Feb 05 '19 at 16:37
  • @CharlesDuffy and others: Thank you- for some reason I thought that it will test the variable and not replace it with its actual content. now it makes sense – lewiatan Feb 05 '19 at 16:40
  • `test` (whether under that name or its synonym `[`) isn't special shell syntax, it's a regular command (as an implementation detail, it happens to be a regular command with a built-in implementation for performance reasons, but that built-in has the exact same behaviors as the external one, so it can't directly inspect any shell state that isn't exposed to external processes). – Charles Duffy Feb 05 '19 at 17:28
  • ...by contrast, `[[ ]]` *is* special syntax, so *that* can directly refer to variables' values, and distinguish between syntax and expansion results. – Charles Duffy Feb 05 '19 at 17:31