if statement in while loop bash - linux

I am trying to make script that inserts hostname and ip address for certain website in mysql , but before the script inserts results i want to check if they already exist and if it does the script should not insert it.
result_check=$(mysql -uroot -p123qwe webs -e "SELECT COUNT(*) AS NUMBER FROM webs WHERE hostname='$hostname' AND ip='$ip'";)
cat $dir/webs | while read hostname ip; do
if [[ $result_check -eq 0 ]]; then
echo "INSERT INTO webs (hostname,ip) VALUES ('$hostname','$ip');"
fi;
done | mysql -uroot -p123qwe webs;
And this somehow does not work , i can think of other way to get the job done(without if) but i want to make it this way.
My webs file looks like that :
somedomain.com 192.168.3.3 ... and etc.
This is the error i get:
line 23: [[: NUMBER
0: syntax error in expression (error token is "0")
I have tried many ways to escape this error (changing the brackets from [[ to (( ) but i've had no luck.

The error message suggests that the output from the first mysql begins with NUMBER. Maybe there's a way to suppress column headers? Ah yes, the -N option.
Secondly, you appear to require the values that are being read from the file to be present in the query, so you have to rearrange things accordingly.
Additionally, you want to clean up the syntax.
while read hostname ip; do
result_check=$(mysql -N -uroot -p123qwe webs \
-e "SELECT COUNT(*) AS NUMBER FROM webs WHERE hostname='$hostname' AND ip='$ip'")
if [[ $result_check -eq 0 ]]; then
echo "INSERT INTO webs (hostname,ip) VALUES ('$hostname','$ip');"
fi
done <"$dir"/webs | mysql -uroot -p123qwe webs
I dislike the temporary variable for the result_check but inlining it would hamper readability, so I guess it'll have to stay.
However, a much better approach would be to use native SQL constructs for INSERT ... WHERE NOT EXISTS or similar. See also How to 'insert if not exists' in MySQL?

Your code is kosher, so $result_check is clearly not a number. Here are some suggestions:
Add an echo if [[ $result_check -eq 0 ]] before the if check to see what's being checked.
In Bash it's sometimes helpful to protect against strings being empty. In this case, the protection would be: if [[ "x$result_check" = x0 ]], but you can't do any interesting integer stuff with that check, for example testing equality for "0" and "00" would fail. if [[ "0$result_check" -eq 0 ]] should solve that problem, but I haven't tested it. On my system, the [[ function actually handles the empty string with no problem, so [[ $undefined_variable -eq 0 ]] is always true.
You can run type [[ to see what the [[ function actually is. For me, it's a shell keyword (bash version 3.2).
For fun, try the [ function instead. As long as you use quotes and add a prefix to protect against empty variables, the result should be the same for these simple tests.

Related

How to search for multiple domain names availability using bash?

I'm trying to search for multiple domain names availability with this script but it doesn't work, where's the mistake? thanks
#!/bin/bash
WEB=('madmane1' 'madmane2' 'madmane3' 'madmane4' 'madmane5' 'madmane6' 'madmane7' 'madmane8' 'madmane9')
ELEMENTS=${#WEB[#]}
for (( i=0;i<$ELEMENTS;i++)); do
whois ${WEB[${i}]}$'.com' | egrep -q \
'^No match|^NOT FOUND|^Not fo|AVAILABLE|^No Data Fou|has not been regi|No entri'
if [ $? -eq 0 ]; then
echo "${WEB[${i}]}$'.com' : available"
fi
done
here's the error:
line 9: ^No match|^NOT FOUND|^Not fo|AVAILABLE|^No Data Fou|has not been regi|No entri: command not found
Assigning stuff to an array and then wrecking it by assigning it to a string without quoting seems like the main mistake here, though it's not the reason you are getting a syntax error.
You also want to avoid testing ”$?” to see if a command succeeded or not, an anti-pattern
See also Correct Bash and shell script variable capitalization
The reason for the error message seems to be a space after the backslash, so you are escaping the space instead of the newline, and thus the shell (in some sense correctly) parses the next line as a new command.
#!/bin/bash
web=('madmane1' 'madmane2' 'madmane3' 'madmane4' 'madmane5' 'madmane6' 'madmane7' 'madmane8' 'madmane9')
for domain in "${web[#]}"; do
if whois "$domain.com" |
grep -Eq '^No match|^NOT FOUND|^Not fo|AVAILABLE|^No Data Fou|has not been regi|No entri'
then
echo "$domain.com: available"
fi
done
Going forward, probably try http://shellcheck.net/ before asking for human assistance.
I think I fixed the problem with https://www.shellcheck.net/
here it is now:
#!/bin/bash
WEB=('madmane1' 'madmane2' 'madmane3' 'madmane4' 'madmane5' 'madmane6' 'madmane7' 'madmane8' 'madmane9')
ELEMENTS=${#WEB[#]}
for (( i=0;i<$ELEMENTS;i++)); do
whois "${WEB[${i}]}"$'.com' | grep -E \
'^No match|^NOT FOUND|^Not fo|AVAILABLE|^No Data Fou|has not been regi|No entri'
if [ $? -eq 0 ]; then
echo "${WEB[${i}]}$'.com' : available"
fi
done

How to match substring with special characters in linux shell?

I wanted to check if a file contains a string in bash. I have done so for regular string characters. However, i need to check a new string which contains double quote ".
Say the string is:
PARAMETER_XYZ="no"
I tried this, but does not work:
ABC=$(cat /etc/file)
if [[ $ABC = *"PARAMETER_XYZ=\"no\""*]] ; then
exit 0
fi
Any suggestions?
grep is the program of choice to look for the presence of strings.
if grep 'PARAMETER_XYZ="no"' /etc/file > /dev/null then
exit 0
fi
You can also still do it with [[ if you really want:
ABC=$(cat /etc/file)
if [[ $ABC = *'PARAMETER_XYZ="no"'* ]] ; then
exit 0
fi
However, if that's actually a config file you're trying to parse, there are better solutions that are less fragile than looking for an exact string. That file looks like it might even be a shell variables file, in which case you could just source it and then check $PARAMETER_XYZ directly.

What would cause the BASH error "[: too many arguments" after taking measures for special characters in strings?

I'm writing a simple script to check some repositories updates and, if needed, I'm making new packages from these updates to install new versions of those programs it refers to (in Arch Linux). So I made some testing before executing the real script.
The problem is that I'm getting the error [: excessive number of arguments (but I think the proper translation would be [: too many arguments) from this piece of code:
# Won't work despite the double quoted $r
if [ "$r" == *"irt"* ]; then
echo "TEST"
fi
The code is fixed by adding double square brackets which I did thanks to this SO answer made by #user568458:
# Makes the code works
if [[ "$r" == *"irt"* ]]; then
echo "TEST"
fi
Note that $r is defined by:
# Double quotes should fix it, right? Those special characters/multi-lines
r="$(ls)"
Also note that everything is inside a loop and the loop progress with success. The problems occurs every time the if comparison matches, not printing the "TEST" issued, jumping straight to the next iteration of the loop (no problem: no code exists after this if).
My question is: why would the error happens every time the string matches? By my understanding, the double quotes would suffice to fix it. Also, If I count on double square brackets to fix it, some shells won't recognize it (refers to the answer mentioned above). What's the alternative?
Shell scripting seems a whole new programming paradigm.. I never quite grasp the details and fail to secure a great source for that.
The single bracket is a shell builtin, as opposed to the double bracket which is a shell keyword. The difference is that a builtin behaves like a command: word splitting, file pattern matching, etc. occur when the shell parses the command. If you have files that match the pattern *irt*, say file1irt.txt and file2irt.txt, then when the shell parses the command
[ "$r" = *irt* ]
it expands $r, matches all files matching the pattern *irt*, and eventually sees the command:
[ expansion_of_r = file1irt.txt file2irt.txt ]
which yields an error. No quotes can fix that. In fact, the single bracket form can't handle pattern matching at all.
On the other hand, the double brackets are not handled like commands; Bash will not perform any word splitting nor file pattern matching, so it really sees
[[ "expansion_of_r" = *irt* ]]
In this case, the right hand side is a pattern, so Bash tests whether the left hand side matches that pattern.
For a portable alternative, you can use:
case "$r" in
(*irt*) echo "TEST" ;;
esac
But now you have a horrible anti-pattern here. You're doing:
r=$(ls)
if [[ "$r" = *irt* ]]; then
echo "TEST"
fi
What I understand is that you want to know whether there are files matching the pattern *irt* in the current directory. A portable possibility is:
for f in *irt*; do
if [ -e "$f" ]; then
echo "TEST"
break
fi
done
Since you're checking for files with a certain file name, I'd suggest to use find explicitly. Something like
r="$(find . -name '*irt*' 2> /dev/null)"
if [ ! -z "$r" ]; then
echo "found: $r"
fi

Bash if else string variable comparison

I am writing a shell script at which I am trying to compare 2 variables that are strings. Every thing works fine as in all the variables have a value from the commands however my if then else statement is not working.
#!/bin/bash
name=$1
for i in {1...10}
do
username=sudo cat /class/rolls/CSCE215-*|awk 'BEGIN {FIELDWIDTHS = "32 20"} {print $2}'|cut -d " " -f6 |sed -n '1,1p'
if ["$name" == "$username"]
then
echo Correct Username
else
echo Incorrect Username
fi
done
All of the tutorials and help online appears to have what I have here but I cannot find out what is wrong with mine.
You are using the "classic test" but it's a good idea to ALWAYS use the newer conditional expression: http://wiki.bash-hackers.org/syntax/ccmd/conditional_expression
if [[ "$name" == "$username" ]]
As far as I know the test command (with a single bracket) doesn't even officially support the "==" operator..
Oh, and of course don't forget the spaces inside the brackets, bash needs them to break the line up into words.
When using test or [, the correct comparison is:
test "$string1" = "string2"
or
[ "$sting1" = "$string2" ]
Note: the single = instead of ==, and always quote sting variables. Further, there is nothing wrong with using the test or [ operators, in fact, they are preferred when portability is needed. They simply lack some of the extended functionality of the [[ operator, such as character class comparison and the ability to use =~.
Now when using the [[ operator, the correct form is:
[[ "$sting1" == "$string2" ]]
Note: as pointed out, quotes are not required when using the [[ operator, but if you get in the habit of always quoting strings, you will be safe in both cases.

BASH scripting: taking in multiple flags

I have this issue where I am doing a menu sort of program where users are supposed to type in different numbers depending on what operation(s) the want to do.
Right now I need to take in multiple flags to be placed in a command, but I can't seem to get it to work.
Catch: I cannot type in the flags directly into one variable and I will probably have to have this case list to be able to quit at will)
function fileCpy {
printf "Choices:\n1. Interactive copy, answer yes/no before doing the copy: \n2. Make backups of existing destination files\n3. Preserve file attributes\n4. Do a recursive copy\n0. Return to main menu\n"
#read choices
read $a
read $b
read $c
for choices in $choice ; do
case "$choices" in
1)
a=-i ;;
2)
b=--backup ;;
3)
c=-p ;;
#4)
#4= -R -v;;
0)
main;;
esac
echo "$a"
echo "$b"
echo "$c"
printf "\nType the name of the file you wish to copy/backup: "
read $fl1
printf "\nType the destination file: "
read $des1
cp $a $b $c $fl1 $des1
done
}
One problem is that read $a etc is wrong: you should write read a if you need the read at all. As it stands, the value read is stored in the variable with the name stored in $a.
Another problem is that it is far from clear to the innocent user that they're supposed to enter 3 lines of information before the script will continue, but the three read lines force that.
Another problem is that you don't read into $choice (via read choice) so the for loop has nothing to do.
Another problem is that your script will inherit the values of any environment variables that happen to be the same as the names of the variables you're using.
Another problem is that you don't quote the file names. It mostly won't matter unless you have a name that contains spaces or other similarly awkward characters.
A cosmetic issue is that the printf statement is ridiculously long. Use one printf per line. Or use echo. Stuff that scrolls off the RHS of the page is bad (though I don't regard 80 characters as a fixed length for lines, there's a quadratic penalty for lines that are longer than 80 — as (length-80)2 increases, the pain of the longer line goes up.
At another level altogether, the interface is modestly grotesque. As an exercise in shell scripting, it makes sense. As an exercise in how to design good shell scripts, it is a very bad design.
A design that might make sense is:
Set variables to empty: a=""; b=""; c=""; etc.
Offer a range of choices similar to those given now, but add an option to execute the command, and another to abandon ship.
Have a loop that reads choices, and sets flags.
When the user chooses execute, exit the loop and prompt for the file names.
If all's well, execute the command.
Note that you should check that the read commands work; if they don't, fail safe (don't damage anything).
Putting all those together (with some slight differences, but the same overall effect — witness the use of local for the variables):
fileCpy()
{
local a b c file dest
echo "Choices:"
echo "0. Return to main menu"
echo "1. Interactive copy, answer yes/no before doing the copy"
echo "2. Make backups of existing destination files"
echo "3. Preserve file attributes"
echo "4. Do a recursive copy"
echo "5. Execute the copy"
while printf "Your choice: " && read choice
do
[ -z "$choice" ] && return 1 # Empty input - failure
case "$choice" in
(0) return 0;;
(1) a="-i";;
(2) b="--backup";;
(3) c="-p";;
(4) d="-R";;
(5) break;;
(*) echo "Unrecognized response ($choice); please enter 0..5";;
esac
done
[ "$choice" != 5 ] && return 1 # EOF - failure
printf "Type the name of the file you wish to copy/backup: "
read file
[ -z "$file" ] && return 1 # Empty file name - failure
printf "Type the name of the destination file/directory: "
read dest
[ -z "$dest" ] && return 1 # Empty file name - failure
cp $a $b $c "$file" "$dest"
}
Test code:
echo "a=$a b=$b c=$c file=$file dest=$dest"
if fileCpy
then : OK
else echo "Failed"
fi
echo "a=$a b=$b c=$c file=$file dest=$dest"
The last block is a simple test harness. It reports on the values of the variables used inside the function, runs the function, reports if the function failed, and re-echoes the variables to demonstrate that they've not been set.
I would not use that interface unless you paid me to do so, but it more or less meets the goal of a training exercise.
if you want to do menu, you can use select construct (or case/esac). See here for info

Resources