Bash IF logic error - bash

I'm just starting to learn bash scripting and to do a sort of hello world program I came up with this:
#!/bin/bash
clear
echo "This is a test script"
echo
echo
f="foo"
b="bar"
COUNTER=10
until [ $COUNTER -lt 0 ]; do
if [ $f="foo" ]; then
echo first
echo $f $b
f="bar"
b="food"
else if [ $f="bar" ]; then
echo second
echo $f $b
f="foo"
b="bar"
else
echo neither
fi
fi
let COUNTER-=1
done
Now the expected outcome is to clear the screen and say:
This is a test script
<space>
<space>
first
foo bar
second
bar food
first
foo bar
second
bar food
...
But instead the outcome is
This is a test script
<space>
<space>
first
foo bar
first
bar food
first
bar food
...
How is is getting passed the first if every time, when the value is "bar". I've tried with quotes, without quotes, double equals...I'm at a loss. can anyone shed some light?

You should add space between after and before brackets on your condition statements:
if [ "$f" = "foo" ]; then
else if [ "$f" = "bar" ]; then
It's also a good idea to place variables around double quotes to prevent word expansion.
In bash as well, it's recommended to use [[ ]] over [ ]:
if [[ "$f" = "foo" ]; then
else if [[ "$f" = "bar" ]]; then
Your loop can also be simplified with for loop:
for ((i = 10; i; --i)); do
...
done
Your extra fi is also probably not needed:
fi
fi

else if [ $f="bar" ]; then
] is actually a (built-in) command, not just part of the shell syntax. It takes one or more arguments, the last of which must be a ]. In order for the arguments to be recognized, they have to be separated by whitespace.
With a single argument (plus the closing ]), the [ command returns false if that argument is an empty string, true if it's non-empty. The argument is sees is $f="bar", which expands to foo=bar, which is non-empty and therefore true.
Adding spaces around the = should fix this:
else if [ $f = "bar" ]; then
You also need spaces after the [ command and before the closing ]; fortunately, you already do.

Related

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" ;
}

Shell Script Syntax Error

At the moment I am working on a blackjack game using shell script. I have most of the script working with functions however the method I am using to find out if the player/computer goes bust doesn't seem to work. Could anyone point me in the right direction. (I am new to shell script.) When running it it will throw syntax errors around the lines that begin elif and sometimes if. It also prints all of the 'echo' outputs in bustConfirm instead of only the one that is true.
Also yes, one of my functions is called bustCheck.
bustConfirm(){
bust='bust'
under='under'
if [ $userBust -eq $bust -a $systemBust -eq $bust ]
then
echo "You both went bust! Be more careful!"
endGameRepeat
elif [ $userBust -eq $bust -a $systemBust -eq $under ]
echo $userName "went bust! Congratulations" $systemName"!"
endGameRepeat
elif [ $userBust -eq $under -a $systemBust -eq $bust ]
then
echo $systemName "went bust! Congratulations" $userName"!"
endGameRepeat
else
echo "Nobody went bust! Well played!"
endGameScores
fi
}
bustCheck(){
if [ "$userScore" -gt 21 ]
then
echo $userName "is bust!"
userBust='bust'
else
userBust='under'
fi
if [ "$systemScore" -gt 21 ]
then
echo $systemName "is bust!"
systemBust='bust'
else
systemBust='under'
fi
bustConfirm
}
The idea is that I wanted to use an && in the bustConfirm function and then an || to get the player is bust or system is bust result if only one of them was bust.
Also just a pointer but in the bustCheck I am seeing userBust and systemBust to contain the words bust or under. I created the variables bust and under for the bustConfirm function.
systemScore, userScore, systemName and userName are set before when the script is running.
Hope I've given enough detail and formatted it properly, first proper post so I apologize if not!
Taking a quick look, I see that the first if statement doesn't have a space after the opening square bracket.
I also recommend you put quotes around your variable names in if statements. This is due to the way shell actually works. The bash shell is extremely intelligent, and before your program has a chance to do anything, it grabs the line, does its magic, and then presents the line to the processor.
For example:
foo=""
if [ $foo = "" ]
then
echo "Foo is blank"
fi
Seems simple enough. However, what happens is that your shell will grab the line, substitute the value of $foo for the string "$foo", and then execute the line. Since $foo is blank, your if statement will become:
if [ = "" ] # That's not right!
then
echo "Foo is blank"
fi
By using quotes, this:
foo=""
if [ "$foo" = "" ]
then
echo "Foo is blank"
fi
becomes:
foo=""
if [ "" = "" ]
then
echo "Foo is blank"
fi
And that is valid. Another thing you can do is use the new test format that uses double square brackets:
foo=""
if [[ $foo = "" ]]
then
echo "Foo is blank"
fi
This will always work even without the extra quotes, and is now recommended unless you have to have your program compatible with the original Bourne shell syntax.
One more thing you can do in debugging your shell script is to use set -xv which turns on verbose debugging. Each statement, before it is executed will be printed, then it will print again after the shell fills in variables, patterns, etc., and then execute. It's a great way to debug your program. Just put set -xv on the line before you want this verbose debugging mode and use set +xv to turn it off. (Yes, the - turns it on and + turns it off.)
Thanks alot David, great answer, could you also tell me what the best way to get the && or equivalent of it within this as I need to find out if they are both bust, or just one etc
As already mentioned in a comment, you can use either one of these two forms:
if [ "$foo" = "bar" ] && [ "$bar" = "foo" ]
or
if [[ $foo = "bar" && $bar = "foo" ]]

How do I compare two strings in if condition in bash

s="STP=20"
if [[ "$x" == *"$s"* ]]
The if condition is always false; why?
Try this: http://tldp.org/LDP/abs/html/comparison-ops.html
string comparison
=
is equal to
if [ "$a" = "$b" ]
There is a difference in testing for equality between [ ... ] and [[ ... ]].
The [ ... ] is an alias to the test command:
STRING1 = STRING2 the strings are equal
However, when using [[ ... ]]
When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below under Pattern Matching. If the shell option nocasematch is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (==) or does not match (!=) the pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string.
The same seems to be true with just the = sign:
$ foo=bar
$ if [[ $foo = *ar ]]
> then
> echo "These patterns match"
> else
> echo "These two strings aren't equal"
> fi
These patterns match
Note the difference:
$ foo=bar
> if [ $foo = *ar ]
> then
> echo "These patterns match"
> else
> echo "These two strings aren't equal"
> fi
These two strings aren't equal
However, there are a few traps with the [ $f00 = *ar ] syntax. This is the same as:
test $foo = *ar
Which means the shell will interpolate glob expressions and variables before executing the statement. If $foo is empty, the command will become equivalent to:
test = *ar # or [ = *ar ]
Since the = isn't a valid comparison operator in test, you'll get an error like:
bash: [: =: unary operator expected
Which means the [ was expecting a parameter found in the test manpage.
And, if I happen to have a file bar in my directory, the shell will replace *ar with all files that match that pattern (in this case bar), so the command will become:
[ $foo = bar ]
which IS true.
To get around the various issues with [ ... ], you should always put quotes around the parameters. This will prevent the shell from interpolating globs and will help with variables that have no values:
[ "$foo" = "*ar" ]
This will test whether the variable $foo is equal to the string *ar. It will work even if $foo is empty because the quotation marks will force an empty string comparison. The quotes around *ar will prevent the shell from interpolating the glob. This is a true equality.
Of course, it just so happens that if you use quotation marks when using [[ ... ]], you'll force a string match too:
foo=bar
if [[ $foo == "*ar" ]]
then
echo "This is a pattern match"
else
echo "These strings don't match"
fi
So, in the end, if you want to test for string equality, you can use either [ ... ] or [[ ... ]], but you must quote your parameters. If you want to do glob pattern matching, you must leave off the quotes, and use [[ ... ]].
To compare two strings in variables x and y for equality, use
if test "$x" = "$y"; then
printf '%s\n' "equal"
else
printf '%s\n' "not equal"
fi
To test whether x appears somewhere in y, use
case $y in
(*"$x"*)
printf '%s\n' "$y contains $x"
;;
(*)
printf '%s\n' "$y does not contain $x"
;;
esac
Note that these constructs are portable to any POSIX shell, not just bash. The [[ ]] construct for tests is not (yet) a standard shell feature.
I do not know where you came up with the *, but you were real close:
s="STP=20"
if [[ "STP=20" == "$s" ]]; then
echo "It worked!"
fi
You need to escape = using \ in the string s="STP=20"
s="STP\=20"
if [[ "STP\=20" == "$s" ]]; then echo Hi; else echo Bye; fi

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

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

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.

Resources