Reputation: 3290
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
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
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