Meaning of "[: too many arguments" error from if [] (square brackets) - bash

I couldn't find any one simple straightforward resource spelling out the meaning of and fix for the following BASH shell error, so I'm posting what I found after researching it.
The error:
-bash: [: too many arguments
Google-friendly version: bash open square bracket colon too many arguments.
Context: an if condition in single square brackets with a simple comparison operator like equals, greater than etc, for example:
VARIABLE=$(/some/command);
if [ $VARIABLE == 0 ]; then
# some action
fi

If your $VARIABLE is a string containing spaces or other special characters, and single square brackets are used (which is a shortcut for the test command), then the string may be split out into multiple words. Each of these is treated as a separate argument.
So that one variable is split out into many arguments:
VARIABLE=$(/some/command);
# returns "hello world"
if [ $VARIABLE == 0 ]; then
# fails as if you wrote:
# if [ hello world == 0 ]
fi
The same will be true for any function call that puts down a string containing spaces or other special characters.
Easy fix
Wrap the variable output in double quotes, forcing it to stay as one string (therefore one argument). For example,
VARIABLE=$(/some/command);
if [ "$VARIABLE" == 0 ]; then
# some action
fi
Simple as that. But skip to "Also beware..." below if you also can't guarantee your variable won't be an empty string, or a string that contains nothing but whitespace.
Or, an alternate fix is to use double square brackets (which is a shortcut for the new test command).
This exists only in bash (and apparently korn and zsh) however, and so may not be compatible with default shells called by /bin/sh etc.
This means on some systems, it might work from the console but not when called elsewhere, like from cron, depending on how everything is configured.
It would look like this:
VARIABLE=$(/some/command);
if [[ $VARIABLE == 0 ]]; then
# some action
fi
If your command contains double square brackets like this and you get errors in logs but it works from the console, try swapping out the [[ for an alternative suggested here, or, ensure that whatever runs your script uses a shell that supports [[ aka new test.
Also beware of the [: unary operator expected error
If you're seeing the "too many arguments" error, chances are you're getting a string from a function with unpredictable output. If it's also possible to get an empty string (or all whitespace string), this would be treated as zero arguments even with the above "quick fix", and would fail with [: unary operator expected
It's the same 'gotcha' if you're used to other languages - you don't expect the contents of a variable to be effectively printed into the code like this before it is evaluated.
Here's an example that prevents both the [: too many arguments and the [: unary operator expected errors: replacing the output with a default value if it is empty (in this example, 0), with double quotes wrapped around the whole thing:
VARIABLE=$(/some/command);
if [ "${VARIABLE:-0}" == 0 ]; then
# some action
fi
(here, the action will happen if $VARIABLE is 0, or empty. Naturally, you should change the 0 (the default value) to a different default value if different behaviour is wanted)
Final note: Since [ is a shortcut for test, all the above is also true for the error test: too many arguments (and also test: unary operator expected)

Just bumped into this post, by getting the same error, trying to test if two variables are both empty (or non-empty). That turns out to be a compound comparison - 7.3. Other Comparison Operators - Advanced Bash-Scripting Guide; and I thought I should note the following:
I used -e thinking it means "empty" at first; but that means "file exists" - use -z for testing empty variable (string)
String variables need to be quoted
For compound logical AND comparison, either:
use two tests and && them: [ ... ] && [ ... ]
or use the -a operator in a single test: [ ... -a ... ]
Here is a working command (searching through all txt files in a directory, and dumping those that grep finds contain both of two words):
find /usr/share/doc -name '*.txt' | while read file; do \
a1=$(grep -H "description" $file); \
a2=$(grep -H "changes" $file); \
[ ! -z "$a1" -a ! -z "$a2" ] && echo -e "$a1 \n $a2" ; \
done
Edit 12 Aug 2013: related problem note:
Note that when checking string equality with classic test (single square bracket [), you MUST have a space between the "is equal" operator, which in this case is a single "equals" = sign (although two equals' signs == seem to be accepted as equality operator too). Thus, this fails (silently):
$ if [ "1"=="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] && [ "1"="1" ] ; then echo A; else echo B; fi
A
$ if [ "1"=="" ] && [ "1"=="1" ] ; then echo A; else echo B; fi
A
... but add the space - and all looks good:
$ if [ "1" = "" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" ] ; then echo A; else echo B; fi
B
$ if [ "1" = "" -a "1" = "1" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" -a "1" == "1" ] ; then echo A; else echo B; fi
B

Another scenario that you can get the [: too many arguments or [: a: binary operator expected errors is if you try to test for all arguments "$#"
if [ -z "$#" ]
then
echo "Argument required."
fi
It works correctly if you call foo.sh or foo.sh arg1. But if you pass multiple args like foo.sh arg1 arg2, you will get errors. This is because it's being expanded to [ -z arg1 arg2 ], which is not a valid syntax.
The correct way to check for existence of arguments is [ "$#" -eq 0 ]. ($# is the number of arguments).

I also faced same problem. #sdaau answer helped me in logical way. Here what I was doing which seems syntactically correct to me but getting too many arguments error.
Wrong Syntax:
if [ $Name != '' ] && [ $age != '' ] && [ $sex != '' ] && [ $birthyear != '' ] && [ $gender != '' ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "Enter all the values"
fi
in above if statement, if I pass the values of variable as mentioned below then also I was getting syntax error
export "Name"="John"
export "age"="31"
export "birthyear"="1990"
export "gender"="M"
With below syntax I am getting expected output.
Correct syntax:
if [ "$Name" != "" -a "$age" != "" -a "$sex" != "" -a "$birthyear" != "" -a "$gender" != "" ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "it failed"
fi
There are few points which we need to keep in mind
use "" instead of ''
use -a instead of &&
put space before and after operator sign like [ a = b], don't use as [ a=b ] in if condition
Hence above solution worked for me !!!

Some times If you touch the keyboard accidentally and removed a space.
if [ "$myvar" = "something"]; then
do something
fi
Will trigger this error message. Note the space before ']' is required.

I have had same problem with my scripts. But when I did some modifications it worked for me. I did like this :-
export k=$(date "+%k");
if [ $k -ge 16 ]
then exit 0;
else
echo "good job for nothing";
fi;
that way I resolved my problem. Hope that will help for you too.

Related

Why is my zshrc function not working when adding arguments?

I am trying to make a function to start, stop or restart from any directory
The code works fine without any arguments but when adding any argument I get the webserver:6: = not found error, when testing the variables everything looks like it should work
function webserver {
#echo $USERNAME
'echo $1
if [ "$1" != "" ]
then
if [ "$1" == "start"]
then
/Users/$USERNAME/start.sh
fi
if [ "$1" == "stop"]
then
/Users/$USERNAME/stop.sh
fi
if [ "$1" == "restart"]
then
/Users/$USERNAME/restart.sh
fi
else
echo "Invalid arguments! Valid arguments are : start stop restart"
fi
}
Why is this code not working?
There's a single-quote before the echo $1 command, which'll cause different trouble. Is that a typo?
The mains problem is that your comparison syntax is wrong; in a [ ] test, use a single = for string equality test, and you need spaces between each syntactic element, including before the final ].
if [ "$1" == "start"] # Bad, will give errors
if [ "$1" = "start" ] # Good, will work as expected
Also, I'd replace that series of if statements with a either a case statement, or a single if ... elif ... elif, since only one branch will ever be taken.
case "$1" in
start)
/Users/$USERNAME/start.sh ;;
stop)
/Users/$USERNAME/stop.sh ;;
restart)
/Users/$USERNAME/restart.sh ;;
*) # This is the case equivalent of "else"
echo "Invalid arguments! Valid arguments are : start stop restart" ;;
esac
like Gordon said, the syntax for zsh is wrong with only one [ ].
according to "==" logical operator and zsh version 5.7.x (installed using Homebrew)
Simple answer: a == is a logical operator only inside [[ … ]] constructs.
And it works also in ksh and bash.
When used outside a [[ … ]] construct a =cmd becomes a filename expansion operator but only in zsh
$ echo ==
zsh: = not found

Bash: `if ! [ $falseSetVar ] ` won't evaluate correctly for me

I have an if statement within a loop. It's set to false initially so I insert a timestamp in a file at the first run of the loop.
I can't seem to get the following to evaluate correctly.
$ConnectionIsCurrently=false
if ! [ $ConnectionIsCurrently ]; then
# changing false to true so this only occurs once.
$ConnectionIsCurrently=true
fi
Here is the full loop:
while [ $i -le $NoOfTests ]; do
ping -c1 -t1 www.google.ie > /dev/null
if [ $? = 0 ]; then
ConTestPASSCount=$((ConTestPASSCount+1))
if ! [ $ConnectionIsCurrently ]; then
printf 'PASSED AT: '
date "+%s"
printf 'PASSED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=true
fi
echo "PASSCount $ConTestPASSCount"
else
ConTestFAILCount=$((ConTestFAILCount+1))
if [ $ConnectionIsCurrently ]; then
printf 'FAILED AT: '
date "+%s"
printf 'FAILED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=false
fi
echo "FAILCount $ConTestFAILCount"
fi
sleep 1
Testcount=$((Testcount+1))
i=$((i+1))
done
The shell doesn't have boolean values, it just operates on strings (or numbers in $(())). The syntax:
if [ $ConnectionIsCurrently ]
tests whether $ConnectionIsCurrently is a non-empty string, and "false" is not empty.
You could use an empty value as falsey, and any non-empty value as truthy.
ConnectionIsCurrently=
if ! [ "$ConnectionIsCurrently" ]; then
ConnectionIsCurrently=true
fi
Note also that you don't put $ before the variable name when you're assigning to it, only when you're reading it. And you should generally quote variables, unless you're sure you want word splitting done. This is especially important when the variable could be empty, as in this case; without the quotes, the [ command doesn't receive any parameter there.
false and true are actually commands (and also bash builtins), so you can run them as commands and act on the exit status:
ConnectionIsCurrently=false
if ! $ConnectionIsCurrently; then
# changing false to true so this only occurs once.
ConnectionIsCurrently=true
fi
The [...] are not required syntax for the if command: [ is just a regular command whose exit status is used by if.
To summarize:
if and while execute a command and branch depending on whether that command succeeds or fails.
false is a command that produces no output and always fails.
true is a command that produces no output and always succeeds.
[ is a command that succeeds or fails depending on the evaluation of the expression preceding the closing ] argument; man test or info test for details. With a single argument (which should be enclosed in double quotes) before the ], [ succeeds if and only if the argument is non-empty. The [ command is typically built into the shell, but it acts like a command; it's not a special shell syntax.
The shell (sh, bash, ksh, zsh) does not have built-in Boolean types or values. There are several common idioms for using Booleans in shell scripts.
A. Assign a variable the string value true or false. Using such a value in an if statement will do the right thing. (This method is my personal favorite.) Note that the strings true and false are the names of commands, not arbitrary strings.
foo=true
if $foo ; then echo OK ; else echo Oops ; fi
B. Assign a variable any arbitrary non-empty value for truthiness, or the empty string (or leave it unset) for falsitude:
foo=yes
if [ "$foo" ] ; then echo OK ; else echo Oops ; fi
foo=""
if [ "$foo" ] ; then echo Oops ; else echo OK ; fi
(The shell treats an unset variable as if it were set to the empty string -- unless you've done set -o nounset, but that's not usually done in scripts.)
C. Pick two arbitrary strings to represent truth and falsehood, and use them consistently. Use string comparisons to test.
foo=TRUE
if [ "$foo" = TRUE ] ; then echo OK ; else echo Oops ; fi
foo=FALSE
if [ "$foo" = TRUE ] ; then echo Oops ; else echo OK ; fi
All of these methods are potentially error-prone. If you forget a $ or misspell one of your conventional strings, you can get bad results with no warning from the shell; for example with method C, the string True will silently be treated as a false condition. Languages with strictly behaving Booleans can avoid these problems. Bash is not such a language.

Shell Script logical operator for conditions

$i="500,600"
$j="600"
if[$i -ne $j]; then
#some line
else
#some line
fi
This if condition is not going inside.
this if condition fails. else is pass.
how is this possible
can someone help me on this
The trick is to consider the "[" sign as a command (in fact it is one indeed), whose last argument must be a "]". So you must ensure that there is a space after [ and all of its arguments go to the proper place. In your case:
if [ "$i" -ne "$j" ]
then
# some code
else
# some code
fi
Since [ is a command, you might want to omit the if structure and use logical operators, taking advantage of lazy evaluation. The following means the same:
[ "$i" -ne "$j" ] && {
echo "hello" ;
echo "world" ;
} || {
echo "bye bye" ;
echo "world" ;
}

Why test for equality in sh scripts in an indirect way?

I often see this construct in sh scripts:
if [ "z$x" = z ]; then echo x is empty; fi
Why don't they just write it like this?
if [ "$x" = "" ]; then echo x is empty; fi
TL;DR short answer
In this construct:
if [ "z$x" = z ]; then echo x is empty; fi
the z is a guard against funny content of $x and many other problems.
If you write it without the z:
if [ "$x" = "" ]; then echo x is empty; fi
and $x contains the string -x you will get:
if [ "-x" = "" ]; then echo x is empty; fi
and that confuses the hell out of some older implementations of [.
If you further omit the quotes around $x and $x contains the string -f foo -o x you will get:
if [ -f foo -o x = "" ]; then echo x is empty; fi
and now it silently checks for something completely different.
the guard will prevent these maybe honest human errors maybe possibly malicious attacks to fall through silently. with the guard you either get the correct result or an error message. read on for an elaborate explanation.
Elaborate explanation
The z in
if [ "z$x" = z ]; then echo x is empty; fi
is called a guard.
To explain why you want the guard I first want to explain the syntax of the bash conditional if. It is important to understand that [ is not part of the syntax. It is a command. It is an alias to the test command. And in most current shells it is a builtin command.
The grammar rule for if is roughly as follows:
if command; then morecommands; else evenmorecommands; fi
(the else part is optional)
command can be any command. Really any command. What bash does when it encounters an if is roughly as follows:
Execute command.
Check the exit status of command.
If exit status is 0 then execute morecommands. If exit status is anything else, and the else part exists, then execute evenmorecommands.
Let's try that:
$ if true; then echo yay; else echo boo; fi
yay
$ if wat; then echo yay; else echo boo; fi
bash: wat: command not found
boo
$ if echo foo; then echo yay; else echo boo; fi
foo
yay
$ if cat foo; then echo yay; else echo boo; fi
cat: foo: No such file or directory
boo
Let's try the test command:
$ if test z = z; then echo yay; else echo boo; fi
yay
And the alias [:
$ if [ z = z ]; then echo yay; else echo boo; fi
yay
You see [ is not part of the syntax. It is just a command.
Note that the z here has no special meaning. It is just a string.
Let's try the [ command outside of an if:
$ [ z = z ]
Nothing happens? It returned an exit status. You can check the exit status with echo $?.
$ [ z = z ]
$ echo $?
0
Let's try unequal strings:
$ [ z = x ]
$ echo $?
1
Because [ is a command it accepts parameters just like any other commands. In fact, the closing ] is also a parameter, a mandatory parameter which must come last. If it is missing the command will complain:
$ [ z = z
bash: [: missing `]'
It is misleading that bash does the complaining. Actually the builtin command [ does the complaining. We can see more clearly who does the complaining when we invoke the system [:
$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
Interestingly the system [ doesn't always insist on a closing ]:
$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
You need a space before the closing ] otherwise it will not be recognized as a parameter:
$ [ z = z]
bash: [: missing `]'
You also need a space after the [ otherwise bash will think you want to execute another command:
$ [z = z]
bash: [z: command not found
This is much more obvious when you use test:
$ testz = z
bash: testz: command not found
Remember [ is just another name for test.
[ can do more than just compare strings. It can compare numbers:
$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
It can also check for the existence of files or directories:
$ [ -f filename ]
$ [ -d dirname ]
See help [ or man [ for more information about the capabilities of [ (or test). man will show you the documentation for the system command. help will show you the documentation for the bash builtin command.
Now that I have covered the bases I can answer your question:
Why do people write this:
if [ "z$x" = z ]; then echo x is empty; fi
and not this:
if [ "$x" = "" ]; then echo x is empty; fi
For brevity I will strip off the if because this is only about [.
The z in this construct:
[ "z$x" = z ]
is a guard against funny content of $x in combination with older implementations of [, and/or a guard against human error like forgetting to quote $x.
What happens when $x has funny content like -f?
This
[ "$x" = "" ]
will become
[ "-f" = "" ]
Some older implementations of [ will get confused when the first parameter starts with a -. The z will make sure that the first parameter never starts with a - regardless of content of $x.
[ "z$x" = "z" ]
will become
[ "z-f" = "z" ]
What happens when you forgot to quote $x? Funny content like -f foo -o x can change the entire meaning of the test.
[ $x = "" ]
will become
[ -f foo -o x = "" ]
The test is now checking for the existence of the file foo and then logical or with whether x is the empty string. The worst part is that you won't even notice because there is no error message, only an exit status. If $x comes from user input this can even be used for malicious attacks.
With the guarding z
[ z$x = z ]
will become
[ z-f foo -o x = z ]
At least you will now get an error message:
$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
The guard also helps against the case of undefined variable instead of the empty string. Some older shells had different behaviour for undefined variable and empty string. This problem is basically solved because in modern shells undefined mostly behaves like an empty string.
Summary:
The quote around $x helps to make the undefined cases behave more like the empty string cases.
The guard before $x helps to further prevent all the other problems mentioned above.
The guard before $x will prevent all these possible errors:
Funny content of $x (code injection by malicious user)
old implementations of [ (getting confused if string begins with -)
forgetting to quote $x (will allow -f foo -o x to subvert the meaning of the test)
undefined $x. (older implementations behave differently if undefined)
The guard will either do the right thing or raise an error message.
Modern implementations of [ have fixed some of the problems and modern shells have some solutions for the other cases, but they have pitfalls of their own. The guarding z is not necessary if you are otherwise carefull, but it makes avoiding mistakes while writing simple tests so much more simpler.
See also:
bash pitfalls about quoting in tests
bash FAQ more details about test
more about test
more about quoting
"test" operator robustness in various shells
For testing zero length, use -z:
if [ -z "$x" ] ; then
echo x is empty
fi
With bash, you can use its [[ that does not need quotes:
if [[ -z $x ]] ; then
echo x is empty
fi
I just found the following in man 1p sh, the documentation of POSIX shell:
Historical systems have also been unreliable given the common construct:
test "$response" = "expected string"
One of the following is a more reliable form:
test "X$response" = "Xexpected string"
test "expected string" = "$response"
Note that the second form assumes that expected string could not be confused with any unary primary. If expected string starts with '-', '(', '!', or even '=', the first form should be used instead.
The short and simple answer: [ is actually not a bash directive, while [[ is. It is instead a symlink to the command line utility test.
Now for why:
Like any other command line utility, test interprets anything starting with a - to be an option. It also considers anything starting with = to be an operator. If you don't prefix your arguments to [ (or test) with an alpha character, there is no guarantee the test will work reliably.
Consider the values:
a=1
b=1
And the evaluation:
[ "$a" = "$b" ] && echo "Yes, they match"
Which is essentially running the following command (test ignores the closing ] when its exec name is [):
test 1 = 1 ] && echo "Yes, they match"
Now consider the values:
a="-lt"
b="-lt"
The argument -lt is an option to test. So when you perform the same test, it expands to:
test -lt = -lt ] && echo "Yes, they match"
Now, this is fine on Linux systems (or at least modern ones), since test has been rewritten to ignore options that precede or succeed the = or != operators. However, on some older UNIX systems, this will break with an error like:
test: -lt: unary operator expected
if you want to make sure x is defined :
if [ ${x:-Z} = 'Z' ];then
echo x is empty
fi

What does the following line of a bash script do?

Usually work in Windows, but trying to setup RabbitMQ on my Mac. Can someone let me know what the line below does?
[ "x" = "x$RABBITMQ_NODE_IP_ADDRESS" ] && [ "x" != "x$NODE_IP_ADDRESS" ] && RABBITMQ_NODE_IP_ADDRESS=${NODE_IP_ADDRESS}
Specifically, I'm curious about the [ "x" = "x$RAB..."] syntax.
If the RABBITMQ_NODE_IP_ADDRESS variable is empty/doesn't exist, it'll evaluate as "x" = "x" , which is true.
So it basically says, if RABBITMQ_NODE_IP_ADDRESS isn't set and NODE_IP_ADDRESS is set, set RABBITMQ_NODE_IP_ADDRESS=NODE_IP_ADDRESS
The "x" is used (somewhat superstitiously*) to prevent errors if the variable is null or unset. Most of the time the quotes take care of that for you. By putting the literal first and the variable second you eliminate errors in cases where the variable contains a string that starts with a dash, since test (aka [) would think it is an operator. In the case of your example, it would be preferable to use the -z and -n operators that test whether a variable is empty (null or unset) or not empty, respectively.
POSIX shells, such as Bourne (works in Bash, too):
[ -z $RABBITMQ_NODE_IP_ADDRESS ] && [ -n $NODE_IP_ADDRESS" ] && RABBITMQ_NODE_IP_ADDRESS=${NODE_IP_ADDRESS}
Bash (and ksh and zsh):
[[ -z $RABBITMQ_NODE_IP_ADDRESS && -n $NODE_IP_ADDRESS" ]] && RABBITMQ_NODE_IP_ADDRESS=${NODE_IP_ADDRESS}
* There may be some shells that need the "x", but some people do that "because it's always been done that way".
The "x" is not always superstitious, even in my relatively new bash (4.0.33).
Let's put the operation between parens. Empty variables are fine:
$ a=""
$ b=""
$ if [ '(' "$a" = "$b" ')' ]; then echo both_equal; fi
both_equal
But the ! operator for instance is not:
$ a='!'
$ if [ '(' "$a" = "$b" ')' ]; then echo both_equal; fi
bash: [: `)' expected, found
This is not a problem if we write "x$a" = "x$b" instead of "$a" = "$b".
The bracket [ is a test operator, which you can think of as an if statement. This is checking to see if the shell variable RABBITMQ_NODE_IP_ADDRESS is empty. Unfortunately, if you try to compare to an empty string "", the shell eliminates it before it does the test and your binary comparison operator only gets one (or maybe zero) operands. To prevent that error, it is a common practice to concatenate an "x" on each side of the =. Thus, instead of
[ "" = "<variable>" ]
becoming
[ = value ]
and yielding an error,
[ "X" = "X<variable>" ]
becomes
[ X = Xvalue ]
and the comparison may continue

Resources