Bash string comparison not working - linux

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

Related

Iterate over the array searching for multiple elements in Bash [duplicate]

This question already has answers here:
Compare/Difference of two arrays in Bash
(9 answers)
Closed 2 years ago.
firstvar="PRIMARY"
secondvar="SECONDARY"
thirdvar="TERTIARY"
array=($firstvar $secondvar $thirdvar)
echo ${array[*]} //prints PRIMARY SECONDARY TERTIARY
I want to iterate over this array in my bash script, and check if the 3 elements (PRIMARY SECONDARY TERTIARY) are present in the array with no specific order.
If yes, echo "Success. If not, echo Failed. What would be a good way to approach this?
EDIT:
I forgot to mention that if an element(e.g. PRIMARY) is missing from the array, it should also print out Primary is missing.
For a similar array:
array_health=($firsthealth $secondhealth $thirdhealth)
These 3 variables can have either 1 or something else
I want to check whether these 3 variables in the array have value=1, how would I check that? They're not initialized as firsthealth=0 or firsthealth=1.
Try bash rematch
[[ ${array[#]} =~ $firstvar ]] && [[ ${array[#]} =~ $secondvar ]] && [[ ${array[#]} =~ $thirdvar ]] && echo ok || echo ko
Then like this
for item in $firstvar $secondvar $thirdvar; {
[[ ${array[#]} =~ $item ]] && echo "$item" || echo "$item is missing"
}
beginner friendly simple loop with simple case counting all vars
followed by test concatenation and simple if statement
array=("$firstvar" "$secondvar" "$thirdvar")
for i in "${array[#]}"
do
echo "$i"
case "$i" in
"$firstvar")
first=$((first+1))
;;
"$secondvar")
second=$((second+1))
;;
"$thirdvar")
third=$((third+1))
;;
esac
done
if [ "$first" ] && [ "$second" ] && [ "$third" ]
then
echo "Success."
else
[ -z "$first" ] && echo "'$firstvar' missing"
[ -z "$second" ] && echo "'$secondvar' missing"
[ -z "$third" ] && echo "'$thirdvar' missing"
fi
regarding your second array basically same, quote your vars
(although it's unclear to me how you want preserve labels as you only have values in array)
array_health=("$firsthealth" "$secondhealth" "$thirdhealth")
uninitialized vars can be test'ed with -z (as above) or have default value with string manipulation
[ -z "$i" ] && echo "${i:-0}"
You can loop over the elements one by one:
flag=0
for ele in "$firstvar" "$secondvar" "$thirdvar"; do
if [[ ! " ${array[#]} " =~ " ${ele} " ]]; then
echo "$ele" is missing
flag=1
fi
done
if [[ flag -eq 0 ]]; then
echo "all there"
fi
If there is a possibility that your elements may have spaces, that will make the test above unreliable. You can use a different delimiter by using printf to add left and right delimiters to characters unlikely to be in your strings:
firstvar="PRIMARY SECONDARY"
secondvar="SECONDARY"
thirdvar="TERTIARY"
array=("$firstvar" "$thirdvar")
flag=0
printf -v tgt "|%s|" "${array[#]}" # |ele1||ele2||eleN|
for ele in "$firstvar" "$secondvar" "$thirdvar"; do
if [[ ! "$tgt" =~ "|${ele}|" ]]; then
echo "$ele" is missing
flag=1
fi
done
if [[ flag -eq 0 ]]; then
echo "all there"
fi
Prints SECONDARY is missing

How to make error message for this code

#!/bin/bash
my_array=(red orange green)
value='green'
for i in "${!my_array[#]}"; do
if [[ "${my_array[$i]}" = "${value}" ]]; then
echo "${i}";
fi
done
This code will print the index of a value in an array, How can i improve it to print error message if the Entry was not in the array
You can add a flag variable,
cleared before the loop,
set when the value is found,
and print the error message after the loop if the flag is still empty.
found=
for i in "${!my_array[#]}"; do
if [[ "${my_array[$i]}" = "${value}" ]]; then
echo "${i}"
found=1
fi
done
if [ ! "$found" ]; then
echo Error: no such value in the array: $value
fi
Alternatively,
you could use an associative array to keep an index of the values and their positions in the array:
my_array=(red orange green)
declare -A index
for ((i = 0; i < ${#my_array[#]}; i++)); do
index[${my_array[$i]}]=$i
done
local value=$1
if [ "${index[$value]}" ]; then
echo "${index[$value]}"
else
echo Error: no such value in the array: $value
fi

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

How to catch a variable from a function and sent it as a parameter to another function?

I use in a function a variable that was defined in another function, from the same script, but I receive an error.
testare()
{
find "$Home" -name "$({!i})"
rez=$?
test -d "$rez"
t1=$?
test -f "$rez"
t2=$?
if [ "$t1" -eq 0 ]
then
return 0
elif [ "$t2" -eq 0 ]
then
return 1
else
return 2
fi
}
menu ()
{
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]
then
echo "Error!"
return
fi
for (( i=2; i <= "$#"; ++i ))
do
testare "$({!i})"
rez=$?
if [ "$rez" -eq 0 ]
then
echo "Director with the same name exists!"
fi
if [ "$rez" -eq 1 ]
then
echo "File with the same name already exists!"
fi
if [ "$rez" -eq 2 ]
then
touch "$({!i})"
fi
done
}
menu $#
What my code should do: I call my script with maximum 9 parameters, the first one indicates the location where i must create files with the names of the other parameters. First i have to check if those names arent already present on my disc. The usage of FOR is mandatory.
The error shows up on the **** line, because of the i variable. I think " i " isnt available at that moment. How could I make this work? :(
I tried also with writing in another file the function and source it on menu, same result..
You can eliminate testare and simply perform the script's function in one routine as follows:
#!/bin/bash
menu() {
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]; then
echo "Error!"
exit 1
fi
for (( i = 2; i <= "$#"; i++ )); do
if [ -d "$1/${!i}" ]; then
printf "Directory '%s' already exists! \n" "${!i}"
elif [ -f "$1/${!i}" ]; then
printf "File '%s' already exists! \n" "${!i}"
else
touch "$1/${!i}"
fi
done
exit 0
}
menu "$#"
But if you want to use the two routines as they are, then you can modify your script as follows:
testare() {
test -d "$1/$2"
t1="$?"
test -f "$1/$2"
t2="$?"
if [ "$t1" -eq 0 ]; then
return 0
elif [ "$t2" -eq 0 ]; then
return 1
else
return 2
fi
}
menu() {
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]; then
echo "Error!"
exit 1
fi
for (( i = 2; i <= "$#"; i++ )); do
testare "$1" "${!i}"
rez="$?"
if [ "$rez" -eq 0 ]; then
printf "Directory '%s' already exists! \n" "${!i}"
elif [ "$rez" -eq 1 ]; then
printf "File '%s' already exists! \n" "${!i}"
else
touch "$1/${!i}"
fi
done
exit 0
}
menu "$#"
Remember: when you are passing any variable as an argument, that parameter to the routine is accessed by $i where i is replaced by any number >=0 referring to the position of the argument from left to right.
For example, in your script, you had $({!i}) within testare, but the variable i is only defined in the menu routine, hence using that variable in testare results in errors. In order access the arguments passed to testare, you should either directly access them, ie. $1, $2 etc. or you should define a variable (in a loop, for example) and access them using that variable as ${!j} for some variable j.
Edit- explanation for first comment's questions:
Consider, for example, that you had an empty folder named dir in your current working directory. Now you want to create files one, two and three in the dir folder. Hence, you pass it to your script as:
$ ./script dir one two three
Thus, "$1"=dir, "$2"=one etc. The line test -d "$1/$2" tests whether $2 is a directory and exists within the $1 folder, ie. whether or not dir/one exists and is a directory. This is necessary because all files need to be tested and created within the specified directory, which always comes as the first argument to the script (as you stated).
In your script, since testare is doing the testing for existence of named file/directory, testare will need access to the dir directory, hence the reason for 2 arguments being passed to testare in the line testare "$1" "${!i}", whereby the first argument is the dir directory, and the second argument is the file to be tested.
As for your question on how many arguments a method should be called with, you should pass on as many arguments as needed to make the routine do what it is supposed to. For example, the routine testare needed to have the dir directory and some specified file, so that it can check whether that file exists within dir. Hence calling testare dir somefile by using testare "$1" "${!i}".
On the other hand, the %s in printf is a placeholder for "string", whose value is provided at the end. For example,
$ printf "This is not a placeholder for %s \n" "numbers"
This is not a placeholder for numbers
$ printf "The placeholder for numbers is %s \n" "%d"
The placeholder for numbers is %d
$ printf "pi as a whole number equals %d\n" 3
pi as a whole number equals 3
Edit 2: If you want to search the /home directory recursively to check whether somefile exists, you can do the following:
#!/bin/bash
file=$(find /home -name "somefile")
if [[ "$file" != "" ]]; then
echo "file exists"
else
echo "file does not exist"
fi
You can try this (comments and suggestions in the script) :
# Explicit function name
found() {
if [[ -f "$1" ]];then
foundtype="file"
# found() success, return 0
return 0
elif [[ -d "$1" ]]; then
foundtype="directory"
return 0
else
# found() failed, return 1
return 1
fi
}
menu () {
if [ $# -lt 2 ] || [ $# -gt 9 ]
then
# Explicit message sent to stderr
echo "Error : you must provide from 2 to 9 parameters" >&2
# exit script with 1 status code (failed)
exit 1
fi
destdir="$1"
shift
# loop over params
for file in "$#"
do
if found "$destdir/$file"; then # found value = found() return code
echo "$destdir/$file" "is an existing" "$foundtype";
continue; # next iteration
else
# create destination dir if doesn't exist (as dir or as file)
[ ! -d "$destdir" ] && [ ! -f "$destdir" ] && mkdir "$destdir"
echo "Creating $destdir/$file" && touch "$destdir/$file"
fi
done
}
menu "$#"

checking if a string is a palindrome

I am trying to check if a string is a palindrome in bash. Here is what I came up with:
#!/bin/bash
read -p "Enter a string: " string
if [[ $string|rev == $string ]]; then
echo "Palindrome"
fi
Now, echo $string|rev gives reversed string. My logic was to use it in the condition for if. That did not work out so well.
So, how can I store the "returned value" from rev into a variable? or use it directly in a condition?
Another variation without echo and unnecessary quoting within [[ ... ]]:
#!/bin/bash
read -p "Enter a string: " string
if [[ $(rev <<< "$string") == "$string" ]]; then
echo Palindrome
fi
A bash-only implementation:
is_palindrome () {
local word=$1
local len=$((${#word} - 1))
local i
for ((i=0; i <= (len/2); i++)); do
[[ ${word:i:1} == ${word:len-i:1} ]] || return 1
done
return 0
}
for word in hello kayak; do
if is_palindrome $word; then
echo $word is a palindrome
else
echo $word is NOT a palindrome
fi
done
Inspired by gniourf_gniourf:
is_palindrome() {
(( ${#1} <= 1 )) && return 0
[[ ${1:0:1} != ${1: -1} ]] && return 1
is_palindrome ${1:1: 1}
}
I bet the performance of this truly recursive call really sucks.
Use $(command substitution):
#!/bin/bash
read -p "Enter a string: " string
if [[ "$(echo "$string" | rev)" == "$string" ]]; then
echo "Palindrome"
fi
Maybe it is not the best implementation, but if you need something with pure sh
#!/bin/sh
#get character <str> <num_of_char>. Please, remember that indexing is from 1
get_character() {
echo "$1" | cut -c "$2"
}
for i in $(seq $((${#1} / 2))); do
if [ "$(get_character "$1" "$i")" != "$(get_character "$1" $((${#1} - i + 1)))" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
and canonical way with bash as well
for i in $(seq 0 $((${#1} / 2 - 1))); do
if [ "${1:$i:1}" != "${1:$((${#1} - i - 1)):1}" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
Skipping all punctuation marks and letter case.
input:He lived as a devil, eh?
output:Palindrome
input:Madam, I am Adam.
output:Not Palindrome
#!/bin/bash
#set -x
read -p "Enter a sentence" message
message=$(echo "$message" | \
sed -e '
s/[[:space:]]//g
s/[[:punct:]]//g
s/\!//g
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
' )
i=0
while read -n 1 letter
do
tempArray[i]="$letter"
((i++))
done < <(echo "$message")
i=0
counter=$((${#message}-1))
while [ "$i" -ne $((${#message}/2)) ]
do
if [ "${tempArray[$i]}" = "${tempArray[$counter]}" ]
then
((i++))
((counter--))
else echo -n "Not ";break
fi
done
echo "Palindrome"
exit

Resources