if statement numerical confusion in bash/sh - bash

I've been arguing with this code all morning. It finally dawned on me, upon reading TL;DP that my if statements may be confusing output redirection with a comparison.
The code in question is:
#!/bin/sh
...
if [ $DEBUG_LEVEL > 2 ]
then
echo "I made it here"
echo "DEBUG: created run_all_somatic_SNV_steps" >>$LOG
fi
Is my if statement confusing stderr redirection with what I want it to do? (compare a variable to the number 2)

For general shell:
if [ $DEBUG_LEVEL -gt 2 ]
(But that will fail if DEBUG_LEVEL has never been set.)
More bash-specific, and a lot nicer:
if (( DEBUG_LEVEL > 2 ))

try this test construct:
[ $DEBUG_LEVEL -gt 2 ]

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

Removing files in Unix using bash

I'm trying to delete a large amount of files from my computer, and I'm trying to write a bash script to do so using the rm command. What I want to know is how to do equality in bash, and why my code (posted below) won't compile. Thank you for your help!
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if ["$i" -eq "$e1"]
then
b="000"
echo $b$
fi
if ["$i" -eq "$e2"]
then
b='00'
fi
if ["$i" -eq "$e3"]
then
b='0'
fi
if ["$i" -eq "$e4"]
then
b =''
fi
if [bash$ expr "$i" % "$e3$ -ne 0]
then
d = $b$c$a
rm d
fi
done
Shell scripts aren't compiled at all.
You need spaces after your [ and before your ].
if [ "$i" -eq "$e1" ]
There's an errant bash$ in there you probably don't want at all. It should probably be a $() operator:
if [ $(expr "$i" % "$e3") -ne 0 ]
You can't have spaces around the = in bash. For example, change b ='' to b='' and d = $b$c$a to d=$b$c$a.
echo $b$ looks like it should be echo $b.
Shell script does not compile it is a scripting language.
Try to fix this line :
if [bash$ expr "$i" % "$e3$ -ne 0]
Make it like below :
if [ $(expr "$i" % "$e3$") -ne 0 ]
You need spaces around the square brackets. The [ is actually a command, and like all commands needs to be delineated by white space.
When you set values for variables in shell, you do not put spaces around the equals signs.
Use quotation marks when doing comparisons and setting values to help delineate your values.
What happens if none of the if conditions are true, and $b isn't set.
What is the logic behind this code. It seems to be a bunch of random stuff. You're incrementing $ from 1 to 10000, but only setting the value of $b on only four of those values. Every 200 steps, you delete a file, but $b may or may not be set even though it's part of the file name.
Did you write this program yourself? Did you try to run it? What errors were you getting? Did you look at the lines referenced by those errors. It looks like you included the bash$ prompt as part of the command.
There were plenty of errors, and I've cleaned most of them up. The cleaned up code is posted below, but it still doesn't mean it will do what you want. All you said is you want to delete "a large amount of files" on your computer, but gave no other criteria. You also said "What I want to know is how to do equality in bash" which is not the question you stated in you header.
Here's the code. Note the changes, and it might lead to whatever answer you were looking for.
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if [ "$i" -eq "$e1" ]
then
b="000"
elif [ "$i" -eq "$e2" ]
then
b='00'
elif [ "$i" -eq "$e3" ]
then
b='0'
elif [ "$i" -eq "$e4" ]
then
b=''
fi
if ! $(($i % $e3))
then
d="$b$c$a"
rm "$d"
fi
done
ERRORS:
Spaces around the [ and ]
The rm "$d" command was originallyrm dwhich would just remove a file namedd`.
if/then statement converted to if/else if.
Rewrote [ $(expr "$1" % "$e3") -ne 0 ].
No need for expr since BASH has $((..)) syntax.
No need for test command ([) since if automatically evaluates zero to true and non-zero to false.
Added quotes.

While loop never ends

I'm working with bash and I'm trying to do something like this:
A=1
while [ $A=1 ]; do
read line
echo $line | grep test >/dev/null
A=$?
echo $A
done
This script never ends even when the grep finishes successfully and A is set to 0. What am I missing here? Below is a sample of the output.
$ ./test.sh
asdf
1
test
0
hm...
1
You need to use the correct comparison operator. Bash has different operators for integer and string comparison.
In addition, you need the correct spacing in the comparison expression.
You need
while [ $A -eq 1 ]; do
See here for more
I find Bash's syntax pretty awful - have you tried something like:
while [ $A -eq 1 ] ... ?
It may be trying to re-assign 1 to $A or something strange like that.
Try:
while [ $A -eq 1 ]; do
Most of the answers have focused on the integer/string and spacing problem, which is fine, but your code looks so unidiomatic that IMO it should be completely re-factored. Let's say the idea is to process lines until one line matches the regex 'test':
while read line; do
if [[ "$line" =~ test ]] && break
# do something with $line
done
Of course this can be simplified further if you take advantage of text processing tools like sed:
sed -e '/test/,$d'
you can do this instead. No need to call external grep.
while true; do
read line
case "$line" in
*test* ) break;;
esac
done
echo $line
Have you not tried this
while [ $A == "1" ]
....
done
Edit: Whoops since Dan mentioned my error I graciously admit my mistake and have edited this accordingly - Thanks Dan for the heads up...
while [ $A -eq 1 ]
....
done
Sorry! :(
Hope this helps,
Best regards,
Tom.
All of your answers are in the Advanced Bash-Scripting guide. It is awesome.

Add (collect) exit codes in bash

I need to depend on few separate executions in a script and don't want to bundle them all in an ugly 'if' statement. I would like to take the exit code '$?' of each execution and add it; at the end, if this value is over a threshold - I would like to execute a command.
Pseudo code:
ALLOWEDERROR=5
run_something
RESULT=$?
..other things..
run_something_else
RESULT=$RESULT + $?
if [ $RESULT -gt ALLOWEDERROR ]
then echo "Too many errors"
fi
Issue: Even though the Internet claims otherwise, bash refuses to treat the RESULT and $? as integer. What is the correct syntax?
Thanks.
You might want to take a look at the trap builtin to see if it would be helpful:
help trap
or
man bash
you can set a trap for errors like this:
#!/bin/bash
AllowedError=5
SomeErrorHandler () {
(( errcount++ )) # or (( errcount += $? ))
if (( errcount > $AllowedError ))
then
echo "Too many errors"
exit $errcount
fi
}
trap SomeErrorHandler ERR
for i in {1..6}
do
false
echo "Reached $i" # "Reached 6" is never printed
done
echo "completed" # this is never printed
If you count the errors (and only when they are errors) like this instead of using "$?", then you don't have to worry about return values that are other than zero or one. A single return value of 127, for example, would throw you over your threshold immediately. You can also register traps for other signals in addition to ERR.
A quick experiment and dip into bash info says:
declare -i RESULT=$RESULT + $?
since you are adding to the result several times, you can use declare at the start, like this:
declare -i RESULT=0
true
RESULT+=$?
false
RESULT+=$?
false
RESULT+=$?
echo $RESULT
2
which looks much cleaner.
declare -i says that the variable is integer.
Alternatively you can avoid declare and use arithmetic expression brackets:
RESULT=$(($RESULT+$?))
Use the $(( ... )) construct.
$ cat st.sh
RESULT=0
true
RESULT=$(($RESULT + $?))
false
RESULT=$(($RESULT + $?))
false
RESULT=$(($RESULT + $?))
echo $RESULT
$ sh st.sh
2
$
For how to add numbers in Bash also see:
help let
If you want to use ALLOWEDERROR in your script, preface it with a $, e.g $ALLOWEDERROR.
As mouviciel mentioned collecting sum of return codes looks rather senseless. Probably, you can use array for accumulating non-zero result codes and check against its length. Example of this approach is below:
#!/bin/sh
declare RESULT
declare index=0
declare ALLOWED_ERROR=1
function write_result {
if [ $1 -gt 0 ]; then
RESULT[index++]=$1
fi
}
true
write_result $?
false
write_result $?
false
write_result $?
echo ${#RESULT[*]}
if [ ${#RESULT[*]} -gt $ALLOWEDERROR ]
then echo "Too many errors"
fi
Here are some ways to perform an addition in bash or sh:
RESULT=`expr $RESULT + $?`
RESULT=`dc -e "$RESULT $? + pq"`
And some others in bash only:
RESULT=$((RESULT + $?))
RESULT=`bc <<< "$RESULT + $?"`
Anyway, exit status on error is not always 1 and its value does not depend on error level, so in the general case there is not much sense to check a sum of statuses against a threshold.

Argument Checking Problem in Bash Script

So basically I am trying to check the arguments that are passed into the script. If it has three arguments and the third argument is a 1, then I want it to continue. I also want it to continue if it has four arguments and the third argument is not a 1.
So basically I thought that I could just do...
if ([ $# -ne 3 ] and [ "$3" -ne "2" ])
then
exit 0
fi
However it seems that Bash does not have and's to use for if's,so then I figured that I could just use nested if's, however now it's complaining still. So this is what I have currently...
if [ $# -ne 3 ]
then
if [ "$3" -ne "1" ]
then
echo "Improper number of arguments.
FORMAT make-csv-data <STUDY> <TAG> <MODE> <SELECT>
Select can be left off if you want all data (Mode=1)
"
exit 0
fi
fi
if [ $# -ne 4 ]
then
if [ "$3" -ne "2" ]
then
echo "Improper number of arguments.
FORMAT make-csv-data <STUDY> <TAG> <MODE> <SELECT>
Select can be left off if you want all data (Mode=1)
"
exit 0
fi
fi
So where am I going wrong? Can I not nest if statements in Bash? Is there a super-zen way of doing this that I'm missing altogether?
Thanks for the any help you could give me.
New Problem...
Now, for some reason or another, the code isn't working at all. There are no errors or anything, it just doesn't work. It doesn't check the number of arguments. I've run the script with no arguments at all and it just skips it like it's not even there.
Weird part is that I was sure that the code was working yesterday. Come back today, not so much. Any ideas on what the problem is? (Sorry, but I have to remove the accepted answer on this.)
if [[ $# = 3 && "$3" != "1" ]]
then
echo "Improper number of arguments.
FORMAT make-csv-data <STUDY> <TAG> <MODE> <SELECT>
Select can be omitted if all data is required (Mode=1)
"
exit 0
fi
if [[ $# > 4 ]]
then
echo "Improper number of arguments.
FORMAT make-csv-data <STUDY> <TAG> <MODE> <SELECT>
Select can be omitted if all data is required (Mode=1)
"
exit 0
fi
EDIT II:
There are a few things that the Bash shell isn't liking about this script that I'm trying to do. I'll probably end up rewriting it in another scripting language and do a few more things that I have in mind for the project. Thanks for the help in any case.
if [ $# -ne 3 -a "$3" -ne "1" ]; then
exit 0
fi
For reference
-a = and
-o = or
Or, you could just use use:
if [[ $# != 3 && "$3" != "1" ]]; then
Please see:
http://bash-hackers.org/wiki/doku.php/commands/classictest#and_and_or
and
http://bash-hackers.org/wiki/doku.php/syntax/ccmd/conditional_expression
Since you're just checking exit/return values with "if", you need to provide something, e.g. a command, that provides meaningful ones based on your tests. [ is such a command, another possibility is the [[ keyword.
The actual correct examples already were mentioned by scragar above, I don't want to just repeat them :)

Resources