if else statement is failing in shell script - shell

I have a very simple shell script which is failing at if-else. And I am not able to understand what is wrong. I checked my syntax and indentation.
echo_usr()
{
read -p "Do you want to preserve $EIP_HOME/conf and $EIP_HOME/installer/.datastore? [Y/N] " usr_in
kill_java()
if [ "$usr_in" == "y*" ] || [ "$usr_in" == "Y*" ]; then
preserve_conf()
else
if [ "$usr_in" == "n*" ] || [ "$usr_in" == "N*" ]; then
reset_home_eip()
else
echo "Invalid input"
fi
fi
reset_db()
}
It is giving me the following error:
syntax error near unexpected token `else'

To call a function with no arguments, just use the name of the function. Empty parentheses are not necessary, and in fact are a syntax error. (Or, more precisely, it's part of the syntax for a function definition, and if you try to use it as a function call you'll get a syntax error message later -- if you're lucky.)
The idea is that a call to a shell function looks and acts like an invocation of an external program.
For example, change:
preserve_conf()
to
preserve_conf
(Using elif rather than else followed by if is a good idea, but it's not the cause of the problem you're seeing.)
There's another problem that you won't see until you get past the syntax error (thanks to Sam for pointing it out in a comment). This:
if [ "$usr_in" == "y*" ] || [ "$usr_in" == "Y*" ]; then
isn't going to work correctly; the == operator used by the [ / test built-in doesn't do wildcard matching. If you're using bash, you can use [[ rather than [, and you can also combine the y and Y cases into a single pattern:
if [[ "$usr_in" == [yY]* ]] ; then
...
elif [[ "$usr_in" == [nN]* ]] ; then
...
fi
If you don't have a shell that supports that syntax, a case statement might be your next best bet -- in fact that's probably even cleaner than the if version:
case "$usr_in" in
[yY]*)
...
;;
[nN]*)
...
;;
*)
...
;;
esac

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

Can I omit the then part in an if statement?

How would be the correct bash syntax for something like this:
if [ "$actual" == "$expected" ]; then
doNothing
else
echo "Error: actual: $actual. Expected: $expected"
fi
I am looking for something that works for all possible values of the variables "actual" and "expected". The content of the variables must not be interpreted/evaluated/expanded in any way. The script does not need to be portable (a bash only solution is ok).
You could use the simplest do-nothing statement available:
if [ "$actual" = "$expected" ]; then
:
else
echo "Error: actual: $actual. Expected: $expected"
fi
(Note: One = not two in [/test.)
But a better idea is to just invert the test and remove the need for that entirely:
if [ "$actual" != "$expected" ]; then
echo "Error: actual: $actual. Expected: $expected"
fi
Did you try:
if [ "$actual" != "$expected" ]; then
echo "Error: actual: $actual. Expected: $expected"
fi
if [[ $actual != $expected ]]
then
echo "Error: actual: $actual. Expected: $expected"
fi
Using the builtin [[ has several advantages over test / [. For one, you don't get bitten if you don't quote variables containing whitespace.
[[ ]] also offers < and > for locale-aware lexicographic sorting, regular expression matching, and =~. Check man bash.
(Note Etan's comment though on at least one dissenting opinion. I haven't yet made up my mind whether this is a disadvantage or a feature to be exploited, but it is sure surprising.)
There's also the thing with putting then on a separate line, but that's just personal preference.

[[ test ]] || <action> format not working in bash script

Following up with a question I asked yesterday, I have a script which runs three tests and reports back on each of them.
Tom Fenech provided me with some code that is simple and should address my concerns. However, it doesn't seem to work as expected.
pass=1
[[ test1 ]] || { echo 'test1 failed'; pass=0 }
[[ test2 ]] || { echo 'test2 failed'; pass=0 }
[[ test3 ]] || { echo 'test3 failed'; pass=0 }
[[ $pass -eq 0 ]] && echo 'one of the tests failed'
Let's just work with one of tests. Suppose I have a variable and I need to compare its value to a number:
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0 }
This always results in an error:
./magic_sysrq.sh: line 64: syntax error near unexpected token `else'
./magic_sysrq.sh: line 64: `else'
For context the script contains an if...elif...else...fi block in which these tests are run. The first (if) block runs code one way depending on the version of RedHat, the second (elif) runs it another way also depending on the RedHat version. The else block just says nothing was done due to an unexpected version.
I always hit the above error with the format of the code that was provided. I can get past the error if I remove the braces. However, this always results in the tests failing regardless of successful changes.
I've tried setting the format to
[[ ${VAR} == '128' ]] || echo "test failed" || pass=0
This isn't right either. It will result in a success message even if something fails. I've tried setting the second logical operator to && but that also results in the tests failed message despite the successful changes.
Can someone shed some light on what I might be doing wrong? I suppose I could just write out all of the if...fi blocks for each test as another suggested but that would be tedious at best.
Syntax.
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0 }
...is missing a semicolon; it needs to be:
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0; }
...otherwise, the } is interpreted as an argument (or, immediately following a variable assignment as here, as a command to run with that assignment applied to the environment), leaving the { unclosed, leading to the syntax error seen.
By contrast:
[[ ${VAR} == '128' ]] || echo "test failed" || pass=0
....is wrong for a different reason: If the echo command succeeds (and an echo command failing with an error is a very uncommon occurance), it'll never proceed to run pass=0. (This is true for any language that implements short-circuiting boolean logic, not just bash).
Curly braces, unlike parentheses, are not inherently special to the shell; they're only recognized in certain positions. Most significantly, a close-brace is only recognized as terminating a code block if it's found where the shell would otherwise expect the beginning of a new statement. That means you have to insert either a newline or a semicolon before every }:
pass=1
[[ test1 ]] || { echo 'test1 failed'; pass=0; }
[[ test2 ]] || { echo 'test2 failed'; pass=0; }
[[ test3 ]] || { echo 'test3 failed'; pass=0; }
[[ $pass -eq 0 ]] && echo 'one of the tests failed'
Note that since the last test is arithmetic, you could use ((...)). For example:
(( pass )) || echo 'one of the tests failed'

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.

BASH: read in while loop

while [ $done = 0 ]
do
echo -n "Would you like to create one? [y/n]: "
read answer
if [ "$(answer)" == "y" ] || [ "$(answer)" == "Y" ]; then
mkdir ./fsm_$newVersion/trace
echo "Created trace folder in build $newVersion"
$done=1
elif [ "$(answer)" == "n" ] || [ "$(answer)" == "N" ]; then
$done=2
else
echo "Not a valid answer"
fi
done
Ok so I have this simple bashscript above that simply just tries to get input from a user and validate it. However I keep getting this error
./test.sh: line 1: answer: command not found
./test.sh: line 1: answer: command not found
./test.sh: line 1: answer: command not found
./test.sh: line 1: answer: command not found
Which I have no idea why because "answer" is nowhere near line 1. So I ran into this article
Which makes sense since it's referring to line 1 and can't find answer. So it seems to be starting a new subshell. However I didn't really understand the solution and can't see how I would apply it to my case. I just wanna get this to work.
$(answer) doesn't substitute the value of the variable answer. It executes answer as a command, and substitutes the output of that command. You want ${answer} everywhere you have $(answer). In this case you can get away with bare $answer too, but overuse of ${...} is good paranoia.
(Are you perhaps used to writing Makefiles? $(...) and ${...} are the same in Makefiles, but the shell is different.)
By the way, you have some other bugs:
In shell, you do not put a dollar sign on the variable name on the left hand side of an assignment. You need to change $done=1 to just done=1 and similarly for $done=2.
You are not being paranoid enough about your variable substitutions. Unless you know for a fact that it does the wrong thing in some specific case, you should always wrap all variable substitutions in double quotes. This affects both the mkdir command and the condition on the while loop.
You are not being paranoid enough about arguments to test (aka [). You need to prefix both sides of an equality test with x so that they cannot be misinterpreted as switches.
== is not portable shell, use = instead (there is no difference in bash, but many non-bash shells do not support == at all).
Put it all together and this is what your script should look like:
while [ "x${done}" = x0 ]; do
echo -n "Would you like to create one? [y/n]: "
read answer
if [ "x${answer}" = xy ] || [ "x${answer}" = xY ]; then
mkdir "./fsm_${newVersion}/trace"
echo "Created trace folder in build $newVersion"
done=1
elif [ "x${answer}" = xn ] || [ "x${answer}" = xN ]; then
done=2
else
echo "Not a valid answer"
fi
done
Which I have no idea why because
"answer" is nowhere near line 1. So I
ran into this article
That's not your problem here.
I ran the script and did not get the error you got. I did receive the error:
./test.sh: line 1: [: -eq: unary operator expected
when I tried to compile though. Defining done fixed this. The following script should work...
#!/bin/bash
done=0
while [ $done -eq 0 ]
do
echo -n "Would you like to create one? [y/n]: "
read answer
if [[ "$(answer)" == "y" || "$(answer)" == "Y" ]]; then
mkdir ./fsm_${newVersion}/trace
echo "Created trace folder in build $newVersion"
$done=1
elif [[ "$(answer)" == "n" || "$(answer)" == "N" ]]; then
$done=2
else
echo "Not a valid answer"
fi
done
...note you were doing string comparisons on your done variable, which you apparently intended to be numeric. It's generally bad form to do string comparison on a numeric type variable, though it will work. Use -eq (arithmetic comparison operator) instead. (Also note that if you kept that test, your string equality would be inconsistent... you had "=" in one spot and "==" in another spot... nitpicking here, but it's helpful to be consistent).
Also, I suggest double brackets for your compound conditionals as they will be more readable if you have longer ones. e.g.
if [[($var1 -eq 0 && $var2 -eq 1) || ($var1 -eq 1 && $var2 -eq 0)]]; then
Just a matter of preference as you only have two conditions, but could be useful in the future.
Also you were missing braces '{' '}' around your newVersion variable.
Finally, I'd suggest putting the line #!/bin/bash on the top of your script. Otherwise its up to your environment to determine what to do with your script, which is a bad idea.

Resources