2

I have a book that stated:

"....For example, if the shell variable symbol contains an equal sign,

look at what happens if you try to test it for zero length:

$ echo $symbol
= 
$ test -z "$symbol" 
sh: test: argument expected 

The = operator has higher precedence than the -z operator ...."

But when I try it on my Ubuntu (running in a virtual machine):

jackson@jackson-VirtualBox:/$ symbol="="
jackson@jackson-VirtualBox:/$ echo $symbol 
=
jackson@jackson-VirtualBox:/$ test -z "$symbol"
jackson@jackson-VirtualBox:/$

Why is my test command's result not sh: test:argument expected?

Is the book wrong?

ilkkachu
  • 138,973
Jacky
  • 161
  • 1
    You're using bash, the book has a different interpreter in mind. – Artem S. Tashkinov Apr 15 '21 at 07:58
  • @ArtemS.Tashkinov You mean Ubuntu in virtual box's interpreter is different from the book? – Jacky Apr 15 '21 at 08:09
  • @Jacky what shell does this book tell you to use? Is it bash or something else? How old is this book? What book is it? – terdon Apr 15 '21 at 08:10
  • @terdon the book use BASH and KORN shell.It is a translated book in 2017(In my country).The english book 's name is Shell Programming in Unix, Linux and OS X, 4th Edition – Jacky Apr 15 '21 at 08:17
  • Strange. I cannot reproduce the behavior the book describes with bash or ksh. – terdon Apr 15 '21 at 08:25
  • @terdon what about using /bin/test? – Chris Davies Apr 15 '21 at 08:27
  • 1
    @roaima ah, good point, but no. Same result: it works correctly (i.e. no error message) in bash, dash and ksh. – terdon Apr 15 '21 at 08:57
  • 1
    It's actually a good thing modern implementations of test don't fail. They first check the number of arguments. This way they know = is the operator in test -z = -z or test -z = +z; and they know = is not an operator in test -z =. – Kamil Maciorowski Apr 15 '21 at 08:58
  • 1
    The Heirloom port of the Bourne shell outputs exactly that error message (sh: test: argument expected). – fra-san Apr 15 '21 at 10:12
  • https://unix.stackexchange.com/a/137483/315749 mentions this issue, pointing out it only affects rare cases (Bourne sh on Solaris <=10, old versions of dash). The most interesting question — why does a book published in 2016 uses an uncommon, non-POSIX shell? — still stands, of course. – fra-san Apr 15 '21 at 11:03
  • The most interesting question — why does a book published in 2016 uses an uncommon, non-POSIX shell? Well it's published in 2016. It might have been written years before. So my assumption was correct after all. – Artem S. Tashkinov Apr 15 '21 at 12:47
  • 5
    @ArtemS.Tashkinov The second edition was published in 1989, and I can't be bothered to research the publication date of the first edition. Clearly this book was not sufficiently revised to account for changes in the last 3 or 4 decades. – Gilles 'SO- stop being evil' Apr 15 '21 at 13:52
  • @fra-san Like most heirloom tools, the heirloom shell is useless as a reference for the behavior of the classical Bourne Shell. The reason is that it is buggy and contains unexpected modified behavior. If you like a true reference, have a look at obosh from the schilytools. – schily May 24 '21 at 05:43

1 Answers1

4

The test command is not told an argument is meant to be an operator or an operand. For example, the shell code symbol='='; test -z "$symbol" invokes the command test with the arguments -z and =. It's up to the test command to figure out that -z is meant to be an operator and = is meant to be an operand to that operator.

In most cases, there is at most one syntactically correct interpretation. For example, since there are no unary postfix operators, the only way to parse two arguments is if the first argument is a unary prefix operator and the second argument is its operand. Thus test -z = can only mean to apply the -z (is-string-empty) operator to the string =.

As soon as there are three or more arguments, some edge cases are ambiguous. For example, test '(' -a ')' could be the -a operator applied to the bare strings ( and ), or the bare string -a in parentheses. (I can't think of an ambiguous parsing with three arguments that gives different results.)

Older implementations of the test command had buggy parsers that did not accept some short unambiguous cases such as -z =: the parser decided that = in second position was a binary operator, then realized that there was no right operand for this operator. Sven Mascheck's Bourne shell page lists historical implementations that bugged on test -a =; this includes both Bourne implementations and ancient versions of ksh.

All modern implementations do what you'd expect if given two or three arguments that are valid. This (and a little more) is mandated by POSIX.

The [ command has the same pitfalls as test because it's the same command except for requiring a ] as the final argument.

The [[ … ]] shell syntax available in some modern shells (ksh, bash, zsh) does not have the same problem because it's parsed differently inside the shell. [[ -z "$symbol" ]] is parsed as the -z operator applied to a string; even if the value of symbol is an equal sign, the word = does not appear literally in the command, so it cannot refer to the operator =.

Unless you're working with ancient systems that haven't been supported by their vendors (or perhaps are still supported in some form in some rare niche industries), you will not encounter shells that choke on test -z "$symbol" in the real world. If a book mentions this, it's seriously out of date; consider using better resources. Better books may be prohibitively expensive in third-world countries, but there's a lot of free stuff on the Internet and some of it is actually good.

Toby Speight
  • 8,678
  • The table from Sven Maschek is outdated. Since 5 years, the Bourne Shell follows the POSIX rules for test to strictly check the number of args first before deciding about the result of a test. – schily May 24 '21 at 05:13