Shell script: Why can't the if statement make a logical comparison? - shell

I'm new to Unix and Linux in general and failed to make a logical comparison within an if statement.
I'm sure this is a very basic mistake but I just can't find the error.
if (7+3 == 10); then
echo "nice this works"
elif (7+3 == 73); then
echo "too bad string concatenation"
else
echo "I really don't understand shell"
fi
Echo: I really don't understand shell.

I would expect you to see this error message twice: 7+3: command not found -- did you?
Single sets of parentheses run the enclosed commands in a subshell, so you're attempting to execute the command 7+3 with two arguments, == and 10 (or 73)
Arithmetic evaluation occurs within double parentheses
if ((7+3 == 10)); then
echo "nice this works"
elif ((7+3 == 73)); then
echo "to bad string concatenation"
else
echo "I really don't understand shell"
fi
nice this works
See http://mywiki.wooledge.org/ArithmeticExpression

The operator your are using == is used for string comparisons. The right way to do it would be with the -eq (equal) operator:
if [ 10 -eq 10 ]; then
echo "10 = 10"
fi
and you also need to use the right parenthesis for the if [ ] (you can look this up in the bash with 'man test')

The correct syntax would be
if [ $((7+3)) -eq 10 ]; then
echo "nice this works"

Related

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" ]]

Unix: If/then statement is always false

This if/then statement in Unix always puts me in the else statement. I am using Bash.
name="Don"
if [ "$name" == "Don" ]; then
echo "Hi Don!"
else
echo "You are not Don. You are: $name"
fi
This is my first Unix shell script, so I'm sure it's something obvious. I've checked against the style guide and other if/then examples, but don't see anything wrong: http://www.dreamsyssoft.com/unix-shell-scripting/ifelse-tutorial.php.
If you're in a POSIX shell don't use ==. Instead use =. == is specific to Bash.
name="Don"
if [ "$name" = "Don" ]; then
echo "Hi Don!"
else
echo "You are not Don. You are: $name"
fi
I executed your script and it jus tworked as expected.
If this it the full code snipped, did you propably forget to call the bash?
I am asking this because when executing the snipped with "sh", it behaves exectly as you said as this is just partial valid for sh.
So I think you missed this:
#!/bin/bash
name="Don"
if [ "$name" == "Don" ]; then
echo "Hi Don!"
else
echo "You are not Don. You are: $name"
fi
There was only one thing wrong in your original code: the line that reads
echo "Hi Don!"
The shell is trying to interpret the special character !
Try putting this line single quotes example:
echo 'Hi Don!'

bash - if return code >=1 re run script, start at the beginning

I have a bash script that prints a heading and tests for a value of "Y" or "N".
When someone enters text that does not equal "Y" or "N", I would like to send them back to the beginning of the script, so it prints the heading and the question again.
I know you can do this with goto but I was wondering if there's a different way because I hear many individuals say you should not use goto or that it is deprecated. Whether true or not, I'd like to see if anyone else has a way to solve this problem.
Thanks in advance.
You could implement it in a loop:
while [ !$exit_loop ]
do
echo "enter choice - "
read -n 1 input
case "$input" in
y|Y) $exit_loop = 1;;
n|N) $exit_loop = 1;;
*) echo "invalid choice";;
esac
done
Personally I find no difference between using a goto/loop or any other means. I'd always say to use what is most suitable for the situation - for yours, I'd use a goto.
e.g. If you have multiple indentations spanning lots of lines, and you need to jump back to the start of a function, I'd use a goto - it's a lot easier to understand in its context.
As a direct answer to your question, you can use exec to replace the current process with another process, or, as the case may be, another fresh copy of the current process.
read -p "Yes? Or no? " yn
case $yn in
[YyNn]) ;;
*) exec "$0" "$#"
esac
If you want a more structured approach, you can use a while or until loop.
Example (slightly simplified) using #Michael's suggestion follows. The exit condition is in the while loop, but the user can also do an intermediary action to decide which action to take:
while [[ ! "${action-}" =~ ^[SsRr]$ ]]
do
echo "What do you want to do?"
read -n 1 -p $'[d]iff, [s]kip, [S]kip all, [r]eplace, [R]eplace all: \n' action
if [[ "${action-}" =~ ^[Dd]$ ]]
then
diff "$target_path" "$source_path"
fi
done
echo "Hello User, are you ready to learn some Linux?"
while true; do
echo "Please enter y/n:"
read a
bfunc() {
if [ "$a" == "y" ]
then
echo "That is great, lets get started!"
echo "This Script is under construction, functionality coming soon"
exit 0
elif [ "$a" == "n" ]
then
echo "That is too bad, see you next time!"
echo "You are now exiting the script"
exit 0
else [ "$a" != "y||n" ]
echo "Please only enter y or n!"
fi
}
bfunc
done

shell script variable use

I'll get to the meat and bones:
MY_VAR=6
until [$MY_VAR = 0]
do
dir/dir_$MY_VAR.log
ps | grep "NAME_$MY_VAR.ksh"
check some things
if [results = ok]
echo "program $MY_VAR sucessful"
else
echo "program $MY_VAR failure"
MY_VAR = `expr $MY_VAR - 1`
done
Now I am getting the following errors MY_VAR not found and [6: not found, so I'm assuming a rather noobish mistake. I feel the logic is sound enough just a simple syntax error I am making somewhere by the looks of the two errors I think it could be in the declaration.
You need to have a space after [ and before ] since [ is actually a command and not a delimiter.
Here is your script re-written in Bash (or ksh):
my_var=6
until ((my_var == 0))
do
dir/dir_$my_var.log # I have no idea what this is supposed to be
ps | grep "NAME_$my_var.ksh"
# check some things
if [[ $results = ok ]]
then
echo "program $my_var successful"
else
echo "program $my_var failure"
((my_var--))
fi
done
However:
for my_var in {6..1}
do
dir/dir_$my_var.log # I have no idea what this is supposed to be
ps | grep "NAME_$my_var.ksh"
# check some things
if [[ $results = ok ]]
then
echo "program $my_var successful"
else
echo "program $my_var failure"
fi
done
Your two errors are caused by:
until [$MY_VAR = 0]
MY_VAR = $(expr $MY_VAR - 1)
[I've used $() instead of backticks because I couldn't get backticks into the code section]
The first problem is the lack of spaces around the square brackets - on both ends. The shell is looking for the command [6 (after expanding $MY_VAR), instead of [ (have a look at /usr/bin/[ - it's actually a program). You should also use -eq to do numeric comparisons. = should work ok here, but leading zeros can break a string comparison where a numeric comparison would work:
until [ "$MY_VAR" -eq 0 ]
The second problem is you have spaces in your variable assignment. When you write MY_VAR = ... the shell is looking for the command MY_VAR. Instead write it as:
MY_VAR=`expr $MY_VAR - 1`
These answers directly answer your questions, but you should study Dennis Williamson's answer for better ways to do these things.

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