Why does "if 0;" not work in shell scripting? - bash

I wrote the following shell script, just to see if I understand the syntax to use if statements:
if 0; then
echo yes
fi
This doesn't work. It yields the error
./iffin: line 1: 0: command not found
what am I doing wrong?

use
if true; then
echo yes
fi
if expects the return code from a command. 0 is not a command. true is a command.
The bash manual doesnt say much on the subject but here it is:
http://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs
You may want to look into the test command for more complex conditional logic.
if test foo = foo; then
echo yes
fi
AKA
if [ foo = foo ]; then
echo yes
fi

To test for numbers being non-zero, use the arithmetic expression:
if (( 0 )) ; then
echo Never echoed
else
echo Always echoed
fi
It makes more sense to use variables than literal numbers, though:
count_lines=$( wc -l < input.txt )
if (( count_lines )) ; then
echo File has $count_lines lines.
fi

Well, from the bash man page:
if list; then list; [ elif list; then list; ] ... [ else list; ] fi
The if list is executed. If its exit status is zero, the then list is executed.
Otherwise, each elif list is executed in turn, and if its exit status is zero,
the corresponding then list is executed and the command completes.
Otherwise, the else list is executed, if present.
The exit status is the exit status of the last command executed,
or zero if no condition tested true.
Which means that argument to if gets executed to get the return code, so in your example you're trying to execute command 0, which apparently does not exist.
What does exist are the commands true, false and test, which is also aliased as [. It allows to write more complex expressions for ifs. Read man test for more info.

Related

How to understand if condition in bash?

I am a little confused about Bash test expression and if test commands:
(1)
#!/bin/bash
count=90
if [ $((count)) ]; then
echo "True "
else
echo "False"
fi
Executing it:
# ./test1.sh
True
(2)
#!/bin/bash
count=90
if $((count)); then
echo "True "
else
echo "False"
fi
Executing it:
# ./test2.sh
./test2.sh: line 5: 90: command not found
False
(3)
#!/bin/bash
count=90
if ((count)); then
echo "True "
else
echo "False"
fi
Executing it:
# ./test3.sh
True
My questions are as follows:
a) For test2.sh, why does it complain "command not found"?
b) Although test1.sh and test3.sh outputs the same result, do they have same meanings in if condition?
Anything that does not produce an error is valid.
That said, there are conventions that people follow when writing shell scripts, and they exist mostly because they make sense.
The if command is a construct that:
runs a command, then on the basis of the exit code of that command,
runs some other command.
For example, your system has programs named true and false on it. So you can make an if construct like:
if true; then
echo TRUE
else
echo FALSE
fi
When you "wrap a condition in square brackets" in shell, what you're really doing is running a command named [. It may be a built-in in your shell, or it may be located at /bin/[, but it's a command either way. Its options appear like conditions, and its purpose is to produce an exit value that will be consumed by the if command.
Now ... when you do arithmetic in bash, you can use constructs like $((...)) which is called "Arithmetic Expansion" because the result of the arithmetic is expanded to replace the expression, as if it were a variable. When you use ((...)), without the preceding dollar sign, then the expression is simply evaluated, rather than printed.
So .. Your first if command tests to see that the arithmetic expansion evaluates to true. Most valid arithmetic should do this. Your second command executes the expansion as if it were a command, which is not. And you get the error telling you that 90 can't be run as a command. And your third command executes the arithmetic, but without expanding it. As with the first option, as long as the arithmetic is valid, the test returns true.
The difference between the first and the third variants is that in the first case, the test command (a.k.a. /bin/[ or your shell's built-in equivalent) is evaluating the results of your arithmetic, the result of which is always true unless you do something silly like try to use decimal numbers, whereas in the third case, a valid arithmetic expression that results in a 0 (zero) will appear to be "false".
To test this difference in your shell, try the following:
$ if [ $(( 2.5 + 2 )) ]; then echo yes; else echo no; fi # ERROR
$ if (( 2.5 + 2 )); then echo yes; else echo no; fi # ERROR, no
$ if [ $(( 2 + 2 )) ]; then echo yes; else echo no; fi # yes
$ if (( 2 + 2 )); then echo yes; else echo no; fi # yes
$ if [ $(( 2 - 2 )) ]; then echo yes; else echo no; fi # yes
$ if (( 2 - 2 )); then echo yes; else echo no; fi # no
Either behaviour may be what you're looking for, but you haven't indicated what problem you're trying to solve, so I can't recommend one over the other.

Assigning the output of a C program in unix shell script and checking the value

Let's say I have a C program that evaluates to either a zero or non zero integer; basically a program that evaluates to a boolean value.
I wish to write a shell script that can find out whether the C program evaluates to zero or not. I am currently trying to assign the return value of the C program to a variable in a shell script but seem to be unable to do so. I currently have;
#!/bin/sh
variable=/path/to/executable input1
I know that assigning values in shell script requires us not to have spaces, but I do not know another way around this, since running this seems to evaluate to an error since the shell interprets input1 as a command, not an input. Is there a way I can do this?
I am also unsure as to how to check the return value of the C program. Should I just use an if statement and check if the C program evaluates to a value equal to zero or not?
This is very basic
#!/bin/sh
variable=`/path/to/executable input1`
or
#!/bin/sh
variable=$(/path/to/executable input1)
and to get the return code from the program use
echo $?
You can assign with backticks or $(...) as shown in iharob's answer.
Another way is to interpret a zero return value as success and evaluate that directly (see manual):
if /path/to/executable input1; then
echo "The return value was 0"
else
echo "The return value was not 0"
fi
Testing with a little dummy program that exits with 0 if fed "yes" and exits with 1 else:
#!/bin/bash
var="$1"
if [[ $var == yes ]]; then
exit 0
else
exit 1
fi
Testing:
$ if ./executable yes; then echo "Returns 0"; else echo "Doesn't return 0"; fi
Returns 0
$ if ./executable no; then echo "Returns 0"; else echo "Doesn't return 0"; fi
Doesn't return 0
If not using Bash: if [ "$var" = "yes" ]; then

How does the "if" shell command work?

I know how the if shell command is used. But how does it actually work? Everything I found about it online only explains how it is used.
Is it a built in feature of the shell or is it just a regular command that I could write myself too?
if echo "test"; then mkdir one; else mkdir two; fi
Does this call an if command with echo "test" as its arguments? Does it "remember" the result, so that the next time a then or else command is executed, they can decide whether to actually run or not?
Or does the shell parse something like this and directly executes echo "test"? If so, why do we need a terminating ; or \n? Couldn't it just scan to the then keyword?
Does this call an if command with echo "test" as its arguments? Does it "remember" the result, so that the next time a then or else command is executed, they can decide whether to actually run or not?
if, then, and else are shell keywords, and are special-cased.
Or does the shell parse something like this and directly executes echo "test"? If so, why do we need a terminating ; or \n? Couldn't it just scan to the then keyword?
It needs the command terminator so that it knows that the arguments to the command have terminated. Otherwise, then will be passed as an argument to the command.
Looks like it only uses the result of the last expression before the then part:
if true; then echo y; else echo n; fi # y
if false; then echo y; else echo n; fi # n
if true; false; then echo y; else echo n; fi # n
if false; true; then echo y; else echo n; fi # y
If the result (i.e. $?) is zero, the then part executes, otherwise the else part. Constructs such as test ..., [ ... ], [[ ... ]], (( ... )) etc. evaluate an expression and returns either true or false which is why they can be used in the if statement.
Bash's man page says the syntax of an if statement is if list; then list; [ elif list; then list; ] ... [ else list; ] fi and that "A list is a sequence of one or more pipelines separated by one of the operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or <newline>."
Since it's a shell builtin, it probably doesn't have to "remember" anything; you could in principle write your own conditional construct by having my_if save the result of executing its argument(s), my_then only execute its argument(s) if it's true, and my_else only if it's false. However this could be easily abused or used incorrectly, while bash's if statement requires a very specific syntax.
But even then you couldn't make it work exactly like if. The above example would become:
#my_if true; false; ... # this wouldn't work since my_if ends at the first ;
my_if 'true; false;'; my_then echo y; my_else echo n; # and perhaps my_fi
Finally you wouldn't (easily) be able to nest your my_ifs unless you use some complex array of stored states or something.
In conclusion, you may be able to replicate some of the if yourself but probably not all of it, at least not in a way that is more meaningful to use than regular if.

Operations on boolean variables

In this question it has been shown how to use neat boolean variables in bash. Is there a way of performing logic operations with such variables? E.g. how to get this:
var1=true
var2=false
# ...do something interesting...
if ! $var1 -a $var2; then <--- doesn't work correctly
echo "do sth"
fi
This does work:
if ! $var1 && $var2; then
echo "do sth"
fi
Maybe somebody can explain why -a and -o operators don't work and &&, ||, ! do?
Okay boys and girls, lesson time.
What's happening when you execute this line?
if true ; then echo 1 ; fi
What's happening here is that the if command is being executed. After that everything that happens is part of the if command.
What if does is it executes one or more commands (or rather, pipelines) and, if the return code from the last command executed was successful, it executes the commands after then until fi is reached. If the return code was not successful the then part is skipped and execution continues after fi.
if takes no switches, its behavior is not modifiable in anyway.
In the example above the command I told if to execute was true. true is not syntax or a keyword, it's just another command. Try executing it by itself:
true
It will print nothing, but it set its return code to 0 (aka "true"). You can more clearly see that it is a command by rewriting the above if statement like this:
if /bin/true ; then echo 1 ; fi
Which is entirely equivalent.
Always returning true from a test is not very useful. It is typical to use if in conjunction with the test command. test is sometimes symlinked to or otherwise known as [. On your system you probably have a /bin/[ program, but if you're using bash [ will be a builtin command. test is a more complex command than if and you can read all about it.
help [
man [
But for now let us say that test performs some tests according to the options you supply and returns with either a successful return code (0) or an unsuccessful one. This allows us to say
if [ 1 -lt 2 ] ; then echo one is less than two ; fi
But again, this is always true, so it's not very useful. It would be more useful if 1 and 2 were variables
read -p' Enter first number: ' first
read -p' Enter second number: ' second
echo first: $first
echo second: $second
if [ $first -lt $second ] ; then
echo $first is less than $second
fi
Now you can see that test is doing its job. Here we are passing test four arguments. The second argument is -lt which is a switch telling test that the first argument and third argument should be tested to see if the first argument is less than the third argument. The fourth argument does nothing but mark the end of the command; when calling test as [ the final argument must always be ].
Before the above if statement is executed the variables are evaluated. Suppose that I had entered 20 for first and 25 for second, after evaluation the script will look like this:
read -p' Enter first number: ' first
read -p' Enter second number: ' second
echo first: 20
echo second: 25
if [ 20 -lt 25 ] ; then
echo 20 is less than 25
fi
And now you can see that when test is executed it will be testing is 20 less than 25?, which is true, so if will execute the then statement.
Bringing it back to the question at hand: What's going on here?
var1=true
var2=false
if ! $var1 -a $var2 ; then
echo $var1 and $var2 are both true
fi
When the if command is evaluated it will become
if ! true -a false ; then
This is instructing if to execute true and passing the arguments -a false to the true command. Now, true doesn't take any switches or arguments, but it also will not produce an error if you supply them without need. This means that it will execute, return success and the -a false part will be ignored. The ! will reverse the success in to a failure and the then part will not be executed.
If you were to replace the above with a version calling test it would still not work as desired:
var1=true
var2=false
if ! [ $var1 -a $var2 ] ; then
echo $var1 and $var2 are both true
fi
Because the if line would be evaluated to
if ! [ true -a false ; ] then
And test would see true not as a boolean keyword, and not as a command, but as a string. Since a non-empty string is treated as "true" by test it will always return success to if, even if you had said
if ! [ false -a yourmom ] ; then
Since both are non-empty strings -a tests both as true, returns success which is reversed with ! and passed to if, which does not execute the then statement.
If you replace the test version with this version
if ! $var1 && $var2 ; then
Then it will be evaluated in to
if ! true && false ; then
And will be processed like this: if executes true which returns success; which is reversed by !; because the return code of the first command was failure the && statement short circuits and false never gets executed. Because the final command executed returned a failure, failure is passed back to if which does not execute the then clause.
I hope this is all clear.
It is perhaps worth pointing out that you can use constructs like this:
! false && true && echo 1
Which does not use if but still checks return codes, because that is what && and || are for.
There is kind of a black art to using test without making any mistakes. In general, when using bash, the newer [[ command should be used instead because it is more powerful and does away with lots of gotchas which must, for compatibility reasons, be kept in [.
Since the original poster did not supply a realistic example of what he's trying to accomplish it's hard to give any specific advice as to the best solution. Hopefully this has been sufficiently helpful that he can now figure out the correct thing to do.
You have mixed here two different syntaxes.
This will work:
if ! [ 1 -a 2 ]; then
echo "do sth"
fi
Note brackets around the expressions.
You need the test command ([ in newer syntax) to use these keys (-a, -o and so on).
But test does nut run commands itself.
If you want to check exit codes of commands you must not use test.

How do I use a file grep comparison inside a bash if/else statement?

When our server comes up we need to check a file to see how the server is configured.
We want to search for the following string inside our /etc/aws/hosts.conf file:
MYSQL_ROLE=master
Then, we want to test whether that string exists and use an if/else statement to run one of two options depending on whether the string exists or not.
What is the BASH syntax for the if statement?
if [ ????? ]; then
#do one thing
else
#do another thing
fi
From grep --help, but also see man grep:
Exit status is 0 if any line was selected, 1 otherwise;
if any error occurs and -q was not given, the exit status is 2.
if grep --quiet MYSQL_ROLE=master /etc/aws/hosts.conf; then
echo exists
else
echo not found
fi
You may want to use a more specific regex, such as ^MYSQL_ROLE=master$, to avoid that string in comments, names that merely start with "master", etc.
This works because the if takes a command and runs it, and uses the return value of that command to decide how to proceed, with zero meaning true and non-zero meaning false—the same as how other return codes are interpreted by the shell, and the opposite of a language like C.
if takes a command and checks its return value. [ is just a command.
if grep -q ...
then
....
else
....
fi
Note that, for PIPE being any command or sequence of commands, then:
if PIPE ; then
# do one thing if PIPE returned with zero status ($?=0)
else
# do another thing if PIPE returned with non-zero status ($?!=0), e.g. error
fi
For the record, [ expr ] is a shell builtin† shorthand for test expr.
Since grep returns with status 0 in case of a match, and non-zero status in case of no matches, you can use:
if grep -lq '^MYSQL_ROLE=master' ; then
# do one thing
else
# do another thing
fi
Note the use of -l which only cares about the file having at least one match (so that grep returns as soon as it finds one match, without needlessly continuing to parse the input file.)
†on some platforms [ expr ] is not a builtin, but an actual executable /bin/[ (whose last argument will be ]), which is why [ expr ] should contain blanks around the square brackets, and why it must be followed by one of the command list separators (;, &&, ||, |, &, newline)
just use bash
while read -r line
do
case "$line" in
*MYSQL_ROLE=master*)
echo "do your stuff";;
*) echo "doesn't exist";;
esac
done <"/etc/aws/hosts.conf"
Below code sample should work:
(echo "hello there" | grep -q "AAA") && [ $? -eq 0 ] && echo "hi" || echo "bye"

Resources