How to understand if condition in bash? - 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.

Related

if statement always goes to the else

I just started learning Bash scripting and i have to do a program that separate between one bit map image to two (the image is broken), I already found on the web how to write loops and statements
but i don't know why my if statement is always goes to the else.
the if is modulo by 2 thats equals to 0
here is the following code
#!/bin/sh
OUTPUT="$(hexdump -v -e '/1 "%02X\n"' merge.bmp)"
echo $OUTPUT
vars=0
count=1
touch one
touch two
for i in $OUTPUT
do
if (($vars%2==0))
then
echo "1"
else
echo "2"
fi
vars=$((vars+count))
done
in the terminal the following error is
./q3.sh: 14: ./q3.sh: 2885%2==0: not found
2
i really don't know why the if always print 2
The shebang line is wrong, it should be:
#!/bin/bash
((expression)) is a bash extension, not available in sh.
The /bin/sh version of the (()) bashism is this:
if test $(($vars % 2)) -eq 0; then
echo "1"
...
fi
Since $(()) knows about variable names, you may even drop the dollar and write
if test $((vars % 2)) -eq 0; then
echo "1"
...
fi

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.

Can bash echo command return non-zero exit code?

Like the title says, is there any case where echo will exit non-zero in bash/sh?
code ex.
until monitor_thing_happens; do
test $retry_counter -eq 0 && echo "thing didn't happen" && exit 1
let "retry_counter--"
echo "tries remaining: ${retry_counter}"
sleep 5
done
In the above example, if echo exits non-zero, the && logic breaks, we never exit 1, and we loop forever. Any danger / edge case where echo can exit non-zero?
Yes, echo has a non-zero return status if there's a write error.
Quoting the bash manual:
'echo'
echo [-neE] [ARG ...]
Output the ARGs, separated by spaces, terminated with a newline.
The return status is 0 unless a write error occurs.
A demonstration:
$ cat foo.bash
#!/bin/bash
echo hello
echo "The echo command returned a status of $?" > /dev/tty
$ ./foo.bash > /dev/full
./foo.bash: line 3: echo: write error: No space left on device
The echo command returned a status of 1
$
/dev/full is a device, similar to /dev/zero except that any attempt to write to it will fail with an ENOSPC error.
Nope, no risk. From man bash:
echo [-neE] [arg ...]
Output the args, separated by spaces, followed by a newline.
The return status is always 0. If -n is specified, the trailing
newline is suppressed. If the -e option is given, interpretation of
the following backslash-escaped characters is enabled. The -E option
disables the interpretation of these escape characters, even on systems
where they are interpreted by default. The xpg_echo shell option may
be used to dynamically determine whether or not echo expands these
escape characters by default. echo does not interpret -- to mean the
end of options. echo interprets the following escape sequences:
Emphasis on "The return status is always 0".
From a code quality standpoint, I would recommend not using test unless you're forced to for shell compatibility reasons. In general, use [[, but for arithmetic expressions you can also use ((:
# The generic way
[[ $retry_counter -eq 0 ]] && echo "Thing didn't happen" && exit 1
# The arithmetic way
(( retry_counter == 0 )) && echo "Thing didn't happen" && exit 1
From help man (bash):
Exit Status:
Returns success unless a write error occurs.
UPDATED
So if you echo to a stream that suddenly fails, you will get another exit code.
Different comments show risks.
You can try
retry_counter=5
while [ retry_counter -gt 0 ]; do
monitor_thing_happens && break
(( retry_counter-- ))
echo "tries remaining: ${retry_counter}"
sleep 5
done
Not without risk ! When the function monitor_things_happen resets the same variable retry_counter the loop wil run a long time.

syntax error: unexpected end of file trying to run a simple bash script

I really can't see what the issue is with my script is. I've considered missing quotations or other syntax errors. There's got to be something I'm missing. It's a very simple while loop script...
#!/bin/bash
c=1
while [ $c -le 5 ]
do
echo "Welcone $c times"
c=$(( c++ ))
done
I should mention that I'm running bash in cygwin on windows 7.
thanks for the help
Change:
c=$(( c++ ))
to
(( c=c+1 ))
When Bash sees: (( var)) it will try and 'do some math' on contents... In this case 'c++' == empty string == '0'; c will always be equal to '1' due to 1st assignment...
From the Bash man page on my Linux system (you may need to review this for Cygwin - could be different...):
((expression))
The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to let "expression".
Also:
id++ id--
variable post-increment and post-decrement
++id --id
variable pre-increment and pre-decrement
After a little testing, the 'pre-increment' seems to do what you are after here - note that you may need to declare 'c' as an integer:
typeset -i c=1
while [ $c -le 5 ]
do
echo "Welcone $c times"
c=++c
# (( c=c+1 ))
done

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

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.

Resources