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[#]}"