Bash while if function - linux

I'm try to run this bash script.
#!/bin/sh
MAX=5
j=1
while [ $((1+$j)) -le $MAX ] do
input=$j
if [ $input -le $j ] then
echo "input=$j,$j,$((j+1)),$((j+2)),$((j+3)),$((j+4))"
else
echo "$input"
fi j=$((j+1))
done
I am writing a bash script and trying to check the order list provided in the argument of the shell values. The ouput has the content as:
input=1,1,2,3,4,5
input=2,2,3,4,5,6
input=3,3,4,5,6,7
input=4,4,5,6,7,8
As what I expect it should give the list in increase order at the each line but the result that i'm looking for is:
input=1,2,3,4,5
input=2,1,3,4,5
input=3,1,2,4,5
input=4,1,2,3,5
input=5,1,2,3,4
Please help me, thanks.

In your script, when you are iterating on variable j, only the while loop is keeping track of the range {1 .. MAX}. Hence, if you are at j=5 in your loop, then running echo on $j,$((j+1)),$((j+2)),$((j+3)),$((j+4)) results 5,6,7,8,9 respectively, which is not what you are looking for.
One approach at a solution is, given a number i, creating a range {1 .. MAX} with i removed. For example, given i=2, creating the list 1,3,4,...,MAX. This can then be concatenated to the final output format as echo "input=$i,$list".
The following range routine creates such a list:
# range() outputs a range of numbers 1 to MAX, but with
# the number 'num' removed from the range.
# Usage: range num MAX
# Example: [ input: range 2 5 ] [ output: 1,3,4,5 ]
range() {
num="$1"
MAX="$2"
for i in $(eval echo {1..$MAX}); do
if [ "$num" -eq "$MAX" ]; then
if [ "$i" -eq $((MAX-1)) ]; then
printf "$i"
break
else
printf "$i,"
fi
elif [ "$i" -eq "$MAX" ]; then
printf "$i"
elif [ "$i" -eq "$num" ]; then
continue
else
printf "$i,"
fi
done
printf "\n"
}
Then your while loop becomes,
j=1
MAX=5
while [ "$j" -le "$MAX" ]; do
list=$(range "$j" "$MAX")
echo "input=$j,$list"
j=$((j+1))
done
whereby list variable is assigned the values of the range created with range "$j" "$MAX", then list is concatenated to final output.
Tests: Assuming the above script is named permute,
# when j=1 and MAX=5
$ ./permute
input=1,2,3,4,5
input=2,1,3,4,5
input=3,1,2,4,5
input=4,1,2,3,5
input=5,1,2,3,4
# when j=1 and MAX=10
$ ./permute
input=1,2,3,4,5,6,7,8,9,10
input=2,1,3,4,5,6,7,8,9,10
input=3,1,2,4,5,6,7,8,9,10
input=4,1,2,3,5,6,7,8,9,10
input=5,1,2,3,4,6,7,8,9,10
input=6,1,2,3,4,5,7,8,9,10
input=7,1,2,3,4,5,6,8,9,10
input=8,1,2,3,4,5,6,7,9,10
input=9,1,2,3,4,5,6,7,8,10
input=10,1,2,3,4,5,6,7,8,9

Related

Shell script to list the files which are a specific size range

Question is to list all the files in a directory which are in a specific size range.
I'm able to check the sizes all the files in the directory but the if statement is not able to use the $FILESIZE as an integer to check its range.
Code:
#!/bin/bash
for i in *.txt
do
FILESIZE=$(stat -c%s "$i")
echo "$FILESIZE"
if [ 9 -lt $((FILESIZE)) -gt 10 ]
then
echo "$i"
fi
done
Output:
Edit:
Got it
#!/bin/bash
for i in *.txt
do
FILESIZE=$(stat -c%s "$i")
val=$((FILESIZE))
if [ $val -gt 9 ]
then
if [ $val -lt 21 ]
then
echo "$i"
echo "size of $i is $val"
fi
fi
done
Thanks

Variables lose their values after loop

I have this script which supposed to read file with numbers in each line, and show the largest number, the smallest one and the sum. During the loop variables change their values but after they return their primary value and I can't fix it.
#!/bin/bash
if [ ! $# -eq 1 ]; then
echo "Invalid number of arguments"
elif [ ! -e $1 ]; then
echo "File doesn't exist"
elif [ ! -s $1 ]; then
echo "File is empty"
else
min=$(head -1 $1)
max=$(head -1 $1)
sum=0
(while read i; do
(( sum+=i ))
if [ $min -gt $i ]; then
min=$i
elif [ $max -lt $i ]; then
max=$i
fi
done
)<$1
fi
echo $min $max $sum
(while read i; do
(( sum+=i ))
if [ $min -gt $i ]; then
min=$i
elif [ $max -lt $i ]; then
max=$i
fi
done
)<$1
The subshell introduced by surrounding the loop with ( ) parentheses causes all the variable modifications inside to be confined to the subshell. A subshell is a child process and child processes have their own copies of variables separate from the parent process's.
while read i; do
(( sum+=i ))
if [ $min -gt $i ]; then
min=$i
elif [ $max -lt $i ]; then
max=$i
fi
done <$1
The subshell's not doing anything useful so the easy solution is to remove it.
I changed the expression in top line of loop to cat $1|while read i and removed brackets but it didn't help.
Pipelines have the same problem as subshells: the left and right sides run in child processes. Stick with the while ... done <file version with no pipeline and no subshell.
The problem is that in Bash, parentheses create a new subshell.
You're creating the variables in that subshell, and those variables go away when the subshell finishes.
$ i=foo; (i=bar; echo $i); echo $i
bar
foo
If you remove the parens from your loop, it works:
while read i; do
(( sum+=i ))
if [ $min -gt $i ]; then
min=$i
elif [ $max -lt $i ]; then
max=$i
fi
done < $1

Read string and convert to INT (BASH)

I have a simple script in Bash to read a number in a file and then compare it with a different threshold. The output is this:
: integer expression expected
: integer expression expected
OK: 3
My code is this:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(cat /tmp/wget.txt | awk 'NR==6')
#output=7
echo $output
if [ $output -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ $output -ge 6 ] && [ $output -lt 11 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
rm /tmp/wget.txt
I know what is the problem, I know that I'm reading a string and I try to compare a int. But I don't know how can I do to read this file and convert the number to read in a int var..
Any ideas?
The problem occurs when $output is the empty string; whether or not you quote the expansion (and you should), you'll get the integer expression required error. You need to handle the empty string explictly, with a default value of zero (or whatever default makes sense).
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' < /tmp/get.txt)
output=${output:-0}
if [ "$output" -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ "$output" -ge 6 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
(If you reach the elif, you already know the value of $output is less than 11; there's no need to check again.)
The problem also occurs, and is consistent with the error message, if output ends with a carriage return. You can remove that with
output=${output%$'\r'}
There are a couple of suggestions from my side regarding your code.
You could explicitly tell bash the output is an integer
declare -i output # See [1]
Change
output=$(cat /tmp/wget.txt | awk 'NR==6') # See [2]
may be better written as
output=$(awk 'NR==6' /tmp/wget.txt )
Change
if [ $output -ge 11 ]
to
if [ "0$output" -ge 11 ] # See [4]
or
if (( output >= 11 )) # Better See [3]
References
Check bash [ declare ].
Useless use of cat. Check [ this ]
Quoting [ this ] answer :
((...)) enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability. Also empty variable automatically defaults to 0 in such a statement.
The zero in the beginning of "0$output" help you deal with empty $output
Interesting
Useless use of cat is a phrase that has been resounding in SO for long. Check [ this ]
[ #chepner ] has dealt with the empty output fiasco using [ bash parameter expansion ] in his [ answer ], worth having a look at.
A simplified script:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' </tmp/wget.txt )
output="$(( 10#${output//[^0-9]} + 0 ))"
(( output >= 11 )) && { echo "CRITICAL: $output"; exit 2; }
(( output >= 6 )) && { echo "WARNING: $output"; exit 1; }
echo "OK: $output"
The key line to cleanup any input is:
output="$(( 10#${output//[^0-9]} + 0 ))"
${output//[^0-9]} Will leave only digits from 0 to 9 (will remove all non-numeric chars).
10#${output//[^0-9]} Will convert output to a base 10 number.
That will correctly convert numbers like 0019
"$(( 10#${output//[^0-9]} + 0 ))" Will produce a zero for a missing value.
Then the resulting number stored in output will be compared to limits and the corresponding output will be printed.
In BASH, It is a good idea to use double brackets for strings:
if [[ testing strings ]]; then
<whatever>
else
<whatever>
fi
Or double parenthesis for integers:
if (( testing ints )); then
<whatever>
else
<whatever>
fi
For example try this:
var1="foo bar"
if [ $var1 == 'foo bar' ]; then
echo "ok"
fi
Result:
$ bash: [: too many arguments
Now, this:
var2="foo bar"
if [[ $a == "foo bar" ]]; then
echo "ok"
fi
Result:
ok
For that, your code in BASH:
if [[ $output -ge 11 ]]; then
echo "CRITICAL: $output"
exit 2
elif [[ $output -ge 6 ]]; then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi

Breaking loop in shell script

The user inputs sentences for which the number of words has to be counted. If the number is greater than three the sentence should be considered Long. If three or fewer then Short.
I know how to do that, but the rest of the task says make a loop so the user can input sentences until he inputs "n". The problem is I when I input "n" it counts it, but that "n" shouldn't be counted. (It should be a break statement.) How can I break that loop before counting the letter "n"?
#!/bin/bash
recenica=0
while [ $recenica != "n" ]
do
echo Unesite recenicu:
read recenica
if [ $recenica = "n" ]; then # ->>>>> this doesn't work
break
fi
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done
You need to use quotes around symbols that expand to more than one word (contain spaces)... this works:
#!/bin/bash
recenica=0
while [ "$recenica" != "n" ] # koristiti dvostruke navodnike ovdje
do
echo Unesite recenicu:
read recenica
if [ "$recenica" = "n" ]; then # koristiti dvostruke navodnike ovdje
break
fi
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done
The problem with read is that it will read the entire line and place the contents in recenica. With one word this is fine for the test:
while [ $recenica != "n" ]
However, if you have read more than one word, then the test will fail due to [: too many arguments (as noted by amdn in his answer regarding quoting)
When you enter n you care only about the first word, so limit your test accordingly using parameter expansion/substring extraction:
while [ ${recenica%% *} != "n" ]; do
That allows you to let the while loop control the break logic reducing the entire structure of your code to:
#!/bin/bash
recenica=0
while [ ${recenica%% *} != "n" ]
do
echo Unesite recenicu:
read recenica
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done
There are a number of ways to do this, but this approach is fine.

Bash string comparison not working

I have the following Bash function:
checkForUpdates() {
checkLatest
ret=$?
if [ $ret != 0 ]; then
return $ret
fi
count=0
for i in $(ssh $__updateuser#$__updatehost "ls $__updatepath/*${latest}*"); do
file="${i##$__updatepath}"
echo "$file" >> $__debuglog
if [ -f $__pkgpath/$file ]; then
remoteHash=$(ssh $__updateuser#$__updatehost "md5sum -b < $__updatepath/${file}")
localHash=$(md5sum -b < $__pkgpath/$file)
echo "${remoteHash:0:32} = ${localHash:0:32}" >> $__debuglog
if [ "${remoteHash:0:32}" != "${localHash:0:32}" ]; then
files[$count]=$file
count=$(($count + 1))
echo "Hashes not matched, adding $i" >> $__debuglog
fi
else
files[$count]=$file
count=$(($count + 1))
echo "$file missing" >> $__debuglog
fi
done
# Verify that the files array isn't empty.
if [ $count != 0 ]; then
return 0
else
return 33
fi
}
For some reason, the remoteHash/localHash comparison always returns true. I added the echo so that I could see the values of the hashes and they are definitely different and I can't figure out where I'm going wrong. I have tried different operators with no success and it is driving me crazy!
this isn't related to your question but more of general advice, first and most important you shouldn't parse the output of ls instead use find -print0 here's an example: http://mywiki.wooledge.org/BashFAQ/001
also consider using [[ instead of [ see: http://mywiki.wooledge.org/BashFAQ/031
now regarding your code, this part:
checkLatest
ret=$?
if [ $ret != 0 ]; then
return $ret
fi
could be written simply as:
checkLatest || return
and you don't need to keep a counter on the index of the array, if you initialize the var as an empty array like files=() you can then append elements to it with files+=("$file") you can get the count with "${#files[#]}"

Resources