1

according to the test(1) manpages:

       -n STRING
              the length of STRING is nonzero

so I expected this to run fine:

[ -n ${var} ] && echo "var is not empty"

I used this logic in a real case, a script like this:

[...]
dne() {
    echo DEBUG: wc -c: $(echo -n $peopkg |wc -c)
    echo DEBUG: pdir: $pdir
    echo "Error: ${package}: doesn't exist in local repos"
    exit 1
}


# packages are listed as such: 
# p/pname/package-version-release-distrelease...
# pname is inconsistent, but it is guaranteed that the first word in package
# (all to lowercase) will match at least one pname.
# NOTE: package-[0-9]* matches package-32bit. *-32bit is a subpackage we want
# to match later, but not when it's not already in pcase.
# So that must be negated too

pfirst=${pcase%%-*}
for pdir in ${p}/${pfirst}*
do
    # check if the glob matched anything at all
    [ ${pdir} = "${p}/${pfirst}*" ] && dne

    peopkg=$(find ${pdir} \
        -name ${package}-[0-9]* \
        ! -name *.delta.eopkg \
        ! -name ${package}-32bit* |sort -rn |head -1)
    echo DEBUG: in-loop peopkg: $peopkg
    echo DEBUG: in-loop wc -c: $(echo -n $peopkg |wc -c)
    echo DEBUG: in-loop test -n: $(test -n $peopkg && echo true || echo false)
    #------------------------------------------------------------#
    # break on ANY match. There's supposed to be only one anyway #
    [ -n ${peopkg} ] && break # <--- [issue here]                #
    #------------------------------------------------------------#
done
[ -z ${peopkg} ] && dne
[...]

what matters here is that when I run this, I get these messages:

DEBUG: in-loop peopkg:
DEBUG: in-loop wc -c: 0
DEBUG: in-loop test -n: true
DEBUG: wc -c: 0
DEBUG: pdir: a/alsa-firmware
Error: alsa-utils: doesn't exist in local repos

This makes zero sense to me.. DEBUG: pdir: a/alsa-firmware indicates that the loop exits always on the first iteration. Which can only happen if the glob pattern a/alsa* matched something AND peopkg was nonzero length.

PS: I'm trying to be POSIX compliant.

Moth
  • 337
  • 2
    What is the simplest code that you can show us, that exhibits this behaviour? (Don't expect people to read lots and lots and lots and lots of code. – ctrl-alt-delor Feb 18 '20 at 10:23
  • 1
    https://unix.stackexchange.com/q/246315/5132 has better answers, which the questioner did not get answered in that one, muru. (-: – JdeBP Feb 18 '20 at 11:26

3 Answers3

6

If var contains the empty string, [ -n $var ] expands (after word-splitting $var) to the words [, -n and ]. That's the one-argument version of test, which tests if that single argument is non-empty. The string -n is not empty, so the test is true.

In the GNU manpage, that's mentioned just after your quoted passage:

   -n STRING
          the length of STRING is nonzero

   STRING equivalent to -n STRING

The problem is of course the lack of quoting, discussed in e.g. Why does my shell script choke on whitespace or other special characters? and When is double-quoting necessary?

Note that it's not only the empty string that will break in the unquoted case. Problems also appear if var contains multiple words:

$ var='foo bar'; [ -n $var ]
bash: [: foo: binary operator expected

Or wildcard characters:

$ var='*'; [ -n $var ]
bash: [: file.txt: binary operator expected
ilkkachu
  • 138,973
0

-n doesn't work with unquoted variables.
Just as the manpage suggests, -n tests for nonzero length strings.
So when testing for whether a variable expands to nothing using it, we must quote that variable.

so the fix to this issue will be [ -n "${peopkg}" ]

Moth
  • 337
0

Yes it seems like no string is not empty. But you should quote variables anyway, because of the case that the value has a space.

E.g.

a="hello, world"
b=""

test -n "$a"
test -n "$b"