Haravikk
Haravikk

Reputation: 3290

Exclamation Point and Too Many Arguments

I tried to setup a conditional in a bash script but I've run into an unusual error; bash is complaining of too many arguments, but everything seems correct to me:

[ "${foo:0:1}" = '!' -o "$foo" = '*' ]

Quite simply, the test should succeed if $foo is either an asterisk, or begins with an exclamation point.

However, when $foo begins with an exclamation point, I get the following error in bash:

bash: [: too many arguments

The weird thing is that it works fine if I omit the second condition like so:

[ "${foo:0:1}" = '!' ]

So it seems like the "${foo:0:1}" evaluating to an exclamation point is somehow interfering with the logical or condition, can anyone explain what's going on here, and what the best way would be to work around it?

It doesn't seem to matter what I'm testing for, for example:

[ "${foo:0:1}" = 'z' -o "$foo" = '*' ]

Will still fail if $foo begins with an exclamation point. This has me concerned, as I have numerous scripts that test characters in the same way that could presumably fail unexpectedly if a value contains an exclamation mark, so I could do with finding a good workaround for this.

Upvotes: 1

Views: 87

Answers (2)

Joao Morais
Joao Morais

Reputation: 1935

You can workaround it by splitting your expression:

[ "${foo:0:1}" = '!' ] || [ "$foo" = '*' ]

So Bash doesn't misinterpret what you mean with '!' - if a string or a not operator.

Upvotes: 3

muru
muru

Reputation: 4896

Unless you aim for POSIX compatibility, prefer [[ over [ when using bash. It is safer, easier and more powerful.

For example, you could use regular expressions:

[[ $foo =~ '^(!|\*$)' ]]

And the [[ construct supports || and &&, which you should prefer over the -o and -a:

[[ "${foo:0:1}" = '!' || "$foo" = '*' ]]

History expansion happens before variable expansion, so foo containing ! shouldn't cause problems due to history expansion. What could be happening is that [ sees ! as a negation operator:

$ help [
[: [ arg... ]
     This is a synonym for the "test" builtin, but the last
    argument must be a literal `]', to match the opening `['.
[[ ... ]]: [[ expression ]]
     Returns a status of 0 or 1 depending on the evaluation of the conditional
    expression EXPRESSION.  Expressions are composed of the same primaries used
    by the `test' builtin, and may be combined using the following operators

        ( EXPRESSION )  Returns the value of EXPRESSION
        ! EXPRESSION    True if EXPRESSION is false; else false

The full command (say, if foo='!abcd') becomes:

[ '!' = '!' -o '!abcd' = '*' ]

Which would be parsed:

NOT(= '!' -o '!abcd' = '*')

And if you try the internal expression, you would get the same error:

$ [ = '!' -o a = a ]
sh: [: too many arguments

A common workaround is to prefix the strings with another character:

[ "z${foo:0:1}" = 'z!' -o "$foo" = '*' ]

Have a look the Bash Hackers' Wiki.

Upvotes: 2

Related Questions