Parameter checks in bash not testing correctly - linux

I've been scratching my head about this for a while... I'm trying to get my code to react like:
If no parameters, go to menu
If more OR less than 4 parameters, call error and go to menu
If exactly 4 parameters, write to file and exit
I can't get this to work in any way, and if you can help that would be majorly appreciated!
username=$1
firstname=$2
surname=$3
password=$4
if test "$#" = 0; then
{
menu
}
elif test "$#" = 4; then
{
echo Error
sleep 2
menu
}
else {
echo Done
echo "$firstname" "$surname" >> "$username".log
echo "$password" >> "$username".log
curdate=$(date +'%d/%m/%Y %H:%M:%S')
echo "$curdate" >> "$username".log
sleep 2
clear
exit
}
fi

In bash, numeric comparisons are not done with = but are done with -eq and its ilk. (= is for string comparison.)
So you want something like this. I'm going to replace your test with the much more common [ notation.
if [ "$#" -eq 0 ] ; then
{
menu
}
elif [ "$#" -eq 4 ] ; then
...
Mort

Related

logic errors in scripts

#!/bin/bash
whilenum=1
while [ $whilenum -lt 5 ]
do
if [ $whilenum == 1 ]
then
function funct1 {
echo The value of whilenum is $whilenum
}
funct1
else break
fi
if (( $whilenum == 2 | $whilenum == 3 ))
then
for animal in dog bird turtle
do
echo $animal
done
fi
if [ $whilenum == 4 ]
then
echo Enter your name
read username
fi
if [ ${#username} > 8 ]
then
echo User name is long
else
echo User name is short
fi
whilenum=$(( $whilenum+1 ))
done
When I adjust the number to test the number nothing happens
I made the while loop increment so it can terminate but not sure if its working. Not getting any syntax errors probably have a few logic errors
the problem is that you are breaking if whilenum is not equal to 1. Therefore, your script will not continue. I think you should remove the else break and you should be able to find that it works.
The syntax works, but you are missing things because you don't indent your code.
The break is done when whilenum -eq 1 and the ${#username} is checked every loop.
Be aware if [ $whilenum == 1 ] looks for a string "1", use -eq or if (( $whilenum == 1 )).
You did try to write code and learn, so I hope you will study the code beneath.
Your code can be improved with a for-loop and a case statement:
function funct1 {
echo The value of whilenum is $whilenum
}
for (( whilenum=1; whilenum < 5; whilenum++ )); do
case $whilenum in
1)
funct1
;;
[23])
for animal in dog bird turtle
do
echo $animal
done
;;
4)
read -p "Enter your name: " username
if [ ${#username} -gt 8 ] # or use if (( ${#username} > 8 ))
then
echo User name is long
else
echo User name is short
fi
;;
*)
echo "The default will not be reached. Put an echo here for when someone changes the for-loop conditions."
;;
esac
done

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 "$#"

How to compare a variable with a variable minus a constant in a linux shell script?

I want to compare a variable with another variable minus a constant in a linux shell script.
In cpp this would look like this:
int index = x;
int max_num = y;
if (index < max_num - 1) {
// do whatever
} else {
// do something else
}
In the shell i tried the following:
index=0
max_num=2
if [ $index -lt ($max_num - 1) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
I also tried:
if [ $index -lt ($max_num-1) ]; then
...
if [ $index -lt $max_num - 1 ]; then
...
if [ $index -lt $max_num-1 ]; then
...
but non of these versions works.
How do you write such a condition correctly?
Regards
The various examples that you tried do not work because no arithmetic operation actually happens in any of the variants that you tried.
You could say:
if [[ $index -lt $((max_num-1)) ]]; then
echo yes
fi
$(( expression )) denotes Arithmetic Expression.
[[ expression ]] is a Conditional Construct.
Portably (plain sh), you could say
if [ "$index" -lt "$((max_num-1))" ]; then
echo yes
fi
Short version
[ "$index" -lt "$((max_num-1))" ] && echo yes;
[ is the test program, but requires the closing ] when called as [. Note the required quoting around variables. The quoting is not needed when using the redundant and inconsistent bash extensions cruft ([[ ... ]]).
In bash, a more readable arithmetic command is available:
index=0
max_num=2
if (( index < max_num - 1 )); then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
The strictly POSIX-compliant equivalent is
index=0
max_num=2
if [ "$index" -lt $((max_num - 1)) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi

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