Int comparison in bash error - bash

I'm trying to compare the left values of two IP adresses, but i get an error:
":a : int expected..."
Here is the code:
IN="195.152.15.1"
IFS='.' read -ra ADDR <<< "$IN"
IN2="196.151.14.1"
IFS='.' read -ra ADDR2 <<< "$IN2"
a=${ADDR[0]}
b=${ADDR2[0]}
if [ a -eq b ]
then
echo "OK"
fi
I tried several combinations but nothing works, any suggestion ?
p.s: i don't want to use the "=" sign, only the "-eq"

Your problem has been solved in the comments. You need to refer to the variables in the comparison:
if [ "$a" -eq "$b" ]
then
echo "OK"
fi
Quotes around variables are always recommended when using [. With bash, you've got a couple of alternatives:
if [[ $a -eq $b ]] # extended test is smart enough to deal with unquoted variables
if (( a == b )) # inside arithmetic context, a and b unambiguously refer to variables
I've heard that the performance of (( is slightly worse than the other options but I doubt that it's significant enough to worry about.
It's worth mentioning that ALL_CAPS variable names should be avoided in scripts as they are for use by the shell. Also, there's no need to read into an array if you only want the first part of each IP:
IFS=. read -r ip1 junk <<< "$in1"
The rest of the line is written to the variable junk, which can be ignored.

change if [ a -eq b ] to if [ $a -eq $b ]

Related

Two while loops behaving strangely, Bash script

I'm new to Bash scripting. I have written a script to help me get some info using ssh from bunch of servers. the IP address of first set of devices are from 101 to 148, and the other set are from 201 to 210.
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
if [ "$SD_counter"==148 ]
then
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done
fi
done > log_SD_HD
echo "Done!"
But for some reason command1 is executed on 192.168.11.101 first, then command2 is executed on ip range 192.168.11.201-192.168.11.210 which is the second while loop.
After that the first while loop continues till the end.
Why is this happening? I want the first while loop to be done before the second while loop. Could someone please point out what I'm doing wrong?
#0x1cf's answer provides the right pointer:
[ "$SD_counter"==148 ] doesn't work as expected.
Specifically: "$SD_counter"==148, based on bash's string synthesizing rules, is expanded to a single string literal: the value of $SD_counter is concatenated with literal ==148, and the resulting string literal is treated as a Boolean.
Since a non-empty string in a Boolean context always evaluates to true, [ "$SD_counter"==148 ] always evaluates to true due to lack of spaces around the ==.
Aside from that: in bash you should use [[ ... ]] rather than [ ... ] - it is more robust and provides more features.
Also note (as #0x1cf notes too) that - if using [ ... ] or [[ ... ]] - using the arithmetic operators is the right choice when dealing with numbers: -eq, -ne, -lt, -le, -gt, or -ge.
Generally, though, using (( ... )) expressions - arithmetic evaluation - provides more flexibility with numbers - see below.
That said, your code can be greatly simplified by using arithmetic evaluation - (( ... )) (see section ARITHMETIC EVALUATION in man bash):
It allows you to use C-style arithmetic and Boolean expressions.
If we combine this with bash's array variables, your code can be simplified to:
#!/usr/bin/env bash
BASE=192.168.11
START_INDICES=( 101 201 )
END_INDICES=( 148 210 )
COMMANDS=( command1 command2 )
numRanges=${#START_INDICES[#]}
for (( range = 0; range < numRanges; ++range )); do
cmd=${COMMANDS[range]}
for (( i=${START_INDICES[range]}; i<=${END_INDICES[range]}; ++i )); do
ip=$BASE.$i
ssh $ip $cmd
done
done > log_SD_HD
Note:
(( ... )) expressions DIFFER from normal bash assignments and conditionals in that you:
need NOT reference variables with $
need NOT double-quote variable references
you MAY have spaces around the assignment operator (=)
you MAY omit spaces around relational operators: (( SD_counter==148 )) DOES work.
( string1 ... ) creates an array with elements string1, ...; ${#arrayVar[#]} returns the count of elements of array variable arrayVar; ${arrayVar[ndx]} returns the element with (0-based) index ndx.
It's better to avoid ALL-UPPERCASE variable names such as BASE, as they may conflict with environment variables, which are by convention typically all-uppercase.
UPDATE
Hint: You can always use #!/bin/bash -x to trace and debug your scripts.
Maybe using two while loop is a good idea, just as V_Maenolis showed. However, to answer your question about what's wrong with your script, try this
Replace
if [ "$SD_counter"==148 ]
with
if [ "$SD_counter" -gt 148 ]
which works for me.
So there are two errors
There should be a space before and after == operator, that is to say, using A == B NOT A==B
The logic of comparing SD_counter == 148 is incorrect. Because when SD_counter hits 148, your script will run into the second while loop, and you'll get 147, 201, ..., 210, 148. Using -gt instead avoids the problem.
There is no reason to nest the loops from what you showed:
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
done> log_SD_HD
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done>> log_SD_HD
echo "Done!"

Multiple If Statements in Bash Script

I am trying to make a bash script with the output based on the input.
My code looks like this:
#!/bin/bash
echo "Letter:"
read a
if a=3
then
echo "LOL"
fi
if a=4
then
echo "ROFL"
fi
But when I enter 3 or 4, I get both LOL and ROFL.
Is there a way for me to get LOL for 3 and ROFL for 4?
Sorry if I'm using incorrect terms and stuff, I'm new to bash scripting.
In bash, a=3 is an assignment, not a test. Use, e.g.:
if [ "$a" = 3 ]
Inside [...], the equal sign tests for string (character) equality. If you want to test for numeric value instead, then use '-eq` as in:
if [ "$a" -eq 3 ]
The quotes around "$a" above are necessary to avoid an "operator" error when a is empty.
bash also offers a conditional expressions that begin with [[ and have a different format. Many like the [[ format better (it avoids, for example, the quote issue mentioned above) but the cost is loss of compatibility with other shells. In particular, note that dash, which is the default shell (/bin/sh) for scripts under Debian-derived distributions, does not have [[.
Bash thinks you're trying to assign a variable by saying a=3. You can do the following to fix this:
Use the = operator whilst referencing the variable with a $, like so: if [[ $a = 3 ]]
Use the -eq operator, which is special and doesn't require you to reference the variable with a $, but may not be compatible with all sh-derived shells: if [[ a -eq 3 ]]. If you wish to use -eq without Bash reference the variable: if [[ $a -eq 3 ]]
Note:
The double square brackets [[ ... ]] are a preferred format with specifically Bash conditionals. [ ... ] is good with any sh-derived shell (zsh, tcsh, etc).
if a=3 will assign value 3 to variable a
unless a is readonly variable, if a=3 always returns TRUE
same for if a=4
To compare variable a with a value, you can do this if [ $a = 3 ]
so the script should change to
#!/bin/bash
echo "Letter:"
read a
if [ $a = 3 ]
then
echo "LOL"
fi
if [ $a = 4 ]
then
echo "ROFL"
fi
Since a is read from user input, there is possibility user key in:
non numeric value
a string with empty space
nothing, user may just press Enter key
so a safer way to check is:
if [ "x$a" = "x3" ]

Removing files in Unix using bash

I'm trying to delete a large amount of files from my computer, and I'm trying to write a bash script to do so using the rm command. What I want to know is how to do equality in bash, and why my code (posted below) won't compile. Thank you for your help!
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if ["$i" -eq "$e1"]
then
b="000"
echo $b$
fi
if ["$i" -eq "$e2"]
then
b='00'
fi
if ["$i" -eq "$e3"]
then
b='0'
fi
if ["$i" -eq "$e4"]
then
b =''
fi
if [bash$ expr "$i" % "$e3$ -ne 0]
then
d = $b$c$a
rm d
fi
done
Shell scripts aren't compiled at all.
You need spaces after your [ and before your ].
if [ "$i" -eq "$e1" ]
There's an errant bash$ in there you probably don't want at all. It should probably be a $() operator:
if [ $(expr "$i" % "$e3") -ne 0 ]
You can't have spaces around the = in bash. For example, change b ='' to b='' and d = $b$c$a to d=$b$c$a.
echo $b$ looks like it should be echo $b.
Shell script does not compile it is a scripting language.
Try to fix this line :
if [bash$ expr "$i" % "$e3$ -ne 0]
Make it like below :
if [ $(expr "$i" % "$e3$") -ne 0 ]
You need spaces around the square brackets. The [ is actually a command, and like all commands needs to be delineated by white space.
When you set values for variables in shell, you do not put spaces around the equals signs.
Use quotation marks when doing comparisons and setting values to help delineate your values.
What happens if none of the if conditions are true, and $b isn't set.
What is the logic behind this code. It seems to be a bunch of random stuff. You're incrementing $ from 1 to 10000, but only setting the value of $b on only four of those values. Every 200 steps, you delete a file, but $b may or may not be set even though it's part of the file name.
Did you write this program yourself? Did you try to run it? What errors were you getting? Did you look at the lines referenced by those errors. It looks like you included the bash$ prompt as part of the command.
There were plenty of errors, and I've cleaned most of them up. The cleaned up code is posted below, but it still doesn't mean it will do what you want. All you said is you want to delete "a large amount of files" on your computer, but gave no other criteria. You also said "What I want to know is how to do equality in bash" which is not the question you stated in you header.
Here's the code. Note the changes, and it might lead to whatever answer you were looking for.
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if [ "$i" -eq "$e1" ]
then
b="000"
elif [ "$i" -eq "$e2" ]
then
b='00'
elif [ "$i" -eq "$e3" ]
then
b='0'
elif [ "$i" -eq "$e4" ]
then
b=''
fi
if ! $(($i % $e3))
then
d="$b$c$a"
rm "$d"
fi
done
ERRORS:
Spaces around the [ and ]
The rm "$d" command was originallyrm dwhich would just remove a file namedd`.
if/then statement converted to if/else if.
Rewrote [ $(expr "$1" % "$e3") -ne 0 ].
No need for expr since BASH has $((..)) syntax.
No need for test command ([) since if automatically evaluates zero to true and non-zero to false.
Added quotes.

unary operator expected in shell script when comparing null value with string

I have two variables
var=""
var1=abcd
Here is my shell script code
if [ $var == $var1 ]; then
do something
else
do something
fi
If I run this code it will prompt a warning
[: ==: unary operator expected
How can I solve this?
Since the value of $var is the empty string, this:
if [ $var == $var1 ]; then
expands to this:
if [ == abcd ]; then
which is a syntax error.
You need to quote the arguments:
if [ "$var" == "$var1" ]; then
You can also use = rather than ==; that's the original syntax, and it's a bit more portable.
If you're using bash, you can use the [[ syntax, which doesn't require the quotes:
if [[ $var = $var1 ]]; then
Even then, it doesn't hurt to quote the variable reference, and adding quotes:
if [[ "$var" = "$var1" ]]; then
might save a future reader a moment trying to remember whether [[ ... ]] requires them.
Why all people want to use '==' instead of simple '=' ? It is bad habit! It used only in [[ ]] expression. And in (( )) too. But you may use just = too! It work well in any case. If you use numbers, not strings use not parcing to strings and then compare like strings but compare numbers. like that
let -i i=5 # garantee that i is nubmber
test $i -eq 5 && echo "$i is equal 5" || echo "$i not equal 5"
It's match better and quicker. I'm expert in C/C++, Java, JavaScript. But if I use bash i never use '==' instead '='. Why you do so?

How to parse $QUERY_STRING from a bash CGI script?

I have a bash script that is being used in a CGI. The CGI sets the $QUERY_STRING environment variable by reading everything after the ? in the URL. For example, http://example.com?a=123&b=456&c=ok sets QUERY_STRING=a=123&b=456&c=ok.
Somewhere I found the following ugliness:
b=$(echo "$QUERY_STRING" | sed -n 's/^.*b=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")
which will set $b to whatever was found in $QUERY_STRING for b. However, my script has grown to have over ten input parameters. Is there an easier way to automatically convert the parameters in $QUERY_STRING into environment variables usable by bash?
Maybe I'll just use a for loop of some sort, but it'd be even better if the script was smart enough to automatically detect each parameter and maybe build an array that looks something like this:
${parm[a]}=123
${parm[b]}=456
${parm[c]}=ok
How could I write code to do that?
Try this:
saveIFS=$IFS
IFS='=&'
parm=($QUERY_STRING)
IFS=$saveIFS
Now you have this:
parm[0]=a
parm[1]=123
parm[2]=b
parm[3]=456
parm[4]=c
parm[5]=ok
In Bash 4, which has associative arrays, you can do this (using the array created above):
declare -A array
for ((i=0; i<${#parm[#]}; i+=2))
do
array[${parm[i]}]=${parm[i+1]}
done
which will give you this:
array[a]=123
array[b]=456
array[c]=ok
Edit:
To use indirection in Bash 2 and later (using the parm array created above):
for ((i=0; i<${#parm[#]}; i+=2))
do
declare var_${parm[i]}=${parm[i+1]}
done
Then you will have:
var_a=123
var_b=456
var_c=ok
You can access these directly:
echo $var_a
or indirectly:
for p in a b c
do
name="var$p"
echo ${!name}
done
If possible, it's better to avoid indirection since it can make code messy and be a source of bugs.
you can break $QUERY down using IFS. For example, setting it to &
$ QUERY="a=123&b=456&c=ok"
$ echo $QUERY
a=123&b=456&c=ok
$ IFS="&"
$ set -- $QUERY
$ echo $1
a=123
$ echo $2
b=456
$ echo $3
c=ok
$ array=($#)
$ for i in "${array[#]}"; do IFS="=" ; set -- $i; echo $1 $2; done
a 123
b 456
c ok
And you can save to a hash/dictionary in Bash 4+
$ declare -A hash
$ for i in "${array[#]}"; do IFS="=" ; set -- $i; hash[$1]=$2; done
$ echo ${hash["b"]}
456
Please don't use the evil eval junk.
Here's how you can reliably parse the string and get an associative array:
declare -A param
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
param["$key"]=$value
done <<<"${QUERY_STRING}&"
If you don't like the key check, you could do this instead:
declare -A param
while IFS='=' read -r -d '&' key value; do
param["$key"]=$value
done <<<"${QUERY_STRING:+"${QUERY_STRING}&"}"
Listing all the keys and values from the array:
for key in "${!param[#]}"; do
echo "$key: ${param[$key]}"
done
I packaged the sed command up into another script:
$cat getvar.sh
s='s/^.*'${1}'=\([^&]*\).*$/\1/p'
echo $QUERY_STRING | sed -n $s | sed "s/%20/ /g"
and I call it from my main cgi as:
id=`./getvar.sh id`
ds=`./getvar.sh ds`
dt=`./getvar.sh dt`
...etc, etc - you get idea.
works for me even with a very basic busybox appliance (my PVR in this case).
To converts the contents of QUERY_STRING into bash variables use the following command:
eval $(echo ${QUERY_STRING//&/;})
The inner step, echo ${QUERY_STRING//&/;}, substitutes all ampersands with semicolons producing a=123;b=456;c=ok which the eval then evaluates into the current shell.
The result can then be used as bash variables.
echo $a
echo $b
echo $c
The assumptions are:
values will never contain '&'
values will never contain ';'
QUERY_STRING will never contain malicious code
While the accepted answer is probably the most beautiful one, there might be cases where security is super-important, and it needs to be also well-visible from your script.
In such a case, first I wouldn't use bash for the task, but if it should be done on some reason, it might be better to avoid these new array - dictionary features, because you can't be sure, how exactly are they escaped.
In this case, the good old primitive solutions might work:
QS="${QUERY_STRING}"
while [ "${QS}" != "" ]
do
nameval="${QS%%&*}"
QS="${QS#$nameval}"
QS="${QS#&}"
name="${nameval%%=*}"
val="${nameval#$name}"
val="${nameval#=}"
# and here we have $name and $val as names and values
# ...
done
This iterates on the name-value pairs of the QUERY_STRING, and there is no way to circumvent it with any tricky escape sequence - the " is a very strong thing in bash, except a single variable name substitution, which is fully controlled by us, nothing can be tricked.
Furthermore, you can inject your own processing code into "# ...". This enables you to allow only your own, well-defined (and, ideally, short) list of the allowed variable names. Needless to say, LD_PRELOAD shouldn't be one of them. ;-)
Furthermore, no variable will be exported, and exclusively QS, nameval, name and val is used.
Following the correct answer, I've done myself some changes to support array variables like in this other question. I added also a decode function of which I can not find the author to give some credit.
Code appears somewhat messy, but it works. Changes and other recommendations would be greatly appreciated.
function cgi_decodevar() {
[ $# -ne 1 ] && return
local v t h
# replace all + with whitespace and append %%
t="${1//+/ }%%"
while [ ${#t} -gt 0 -a "${t}" != "%" ]; do
v="${v}${t%%\%*}" # digest up to the first %
t="${t#*%}" # remove digested part
# decode if there is anything to decode and if not at end of string
if [ ${#t} -gt 0 -a "${t}" != "%" ]; then
h=${t:0:2} # save first two chars
t="${t:2}" # remove these
v="${v}"`echo -e \\\\x${h}` # convert hex to special char
fi
done
# return decoded string
echo "${v}"
return
}
saveIFS=$IFS
IFS='=&'
VARS=($QUERY_STRING)
IFS=$saveIFS
for ((i=0; i<${#VARS[#]}; i+=2))
do
curr="$(cgi_decodevar ${VARS[i]})"
next="$(cgi_decodevar ${VARS[i+2]})"
prev="$(cgi_decodevar ${VARS[i-2]})"
value="$(cgi_decodevar ${VARS[i+1]})"
array=${curr%"[]"}
if [ "$curr" == "$next" ] && [ "$curr" != "$prev" ] ;then
j=0
declare var_${array}[$j]="$value"
elif [ $i -gt 1 ] && [ "$curr" == "$prev" ]; then
j=$((j + 1))
declare var_${array}[$j]="$value"
else
declare var_$curr="$value"
fi
done
I would simply replace the & to ;. It will become to something like:
a=123;b=456;c=ok
So now you need just evaluate and read your vars:
eval `echo "${QUERY_STRING}"|tr '&' ';'`
echo $a
echo $b
echo $c
A nice way to handle CGI query strings is to use Haserl which acts as a wrapper around your Bash cgi script, and offers convenient and secure query string parsing.
To bring this up to date, if you have a recent Bash version then you can achieve this with regular expressions:
q="$QUERY_STRING"
re1='^(\w+=\w+)&?'
re2='^(\w+)=(\w+)$'
declare -A params
while [[ $q =~ $re1 ]]; do
q=${q##*${BASH_REMATCH[0]}}
[[ ${BASH_REMATCH[1]} =~ $re2 ]] && params+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})
done
If you don't want to use associative arrays then just change the penultimate line to do what you want. For each iteration of the loop the parameter is in ${BASH_REMATCH[1]} and its value is in ${BASH_REMATCH[2]}.
Here is the same thing as a function in a short test script that iterates over the array outputs the query string's parameters and their values
#!/bin/bash
QUERY_STRING='foo=hello&bar=there&baz=freddy'
get_query_string() {
local q="$QUERY_STRING"
local re1='^(\w+=\w+)&?'
local re2='^(\w+)=(\w+)$'
while [[ $q =~ $re1 ]]; do
q=${q##*${BASH_REMATCH[0]}}
[[ ${BASH_REMATCH[1]} =~ $re2 ]] && eval "$1+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})"
done
}
declare -A params
get_query_string params
for k in "${!params[#]}"
do
v="${params[$k]}"
echo "$k : $v"
done
Note the parameters end up in the array in reverse order (it's associative so that shouldn't matter).
why not this
$ echo "${QUERY_STRING}"
name=carlo&last=lanza&city=pfungen-CH
$ saveIFS=$IFS
$ IFS='&'
$ eval $QUERY_STRING
$ IFS=$saveIFS
now you have this
name = carlo
last = lanza
city = pfungen-CH
$ echo "name is ${name}"
name is carlo
$ echo "last is ${last}"
last is lanza
$ echo "city is ${city}"
city is pfungen-CH
#giacecco
To include a hiphen in the regex you could change the two lines as such in answer from #starfry.
Change these two lines:
local re1='^(\w+=\w+)&?'
local re2='^(\w+)=(\w+)$'
To these two lines:
local re1='^(\w+=(\w+|-|)+)&?'
local re2='^(\w+)=((\w+|-|)+)$'
For all those who couldn't get it working with the posted answers (like me),
this guy figured it out.
Can't upvote his post unfortunately...
Let me repost the code here real quick:
#!/bin/sh
if [ "$REQUEST_METHOD" = "POST" ]; then
if [ "$CONTENT_LENGTH" -gt 0 ]; then
read -n $CONTENT_LENGTH POST_DATA <&0
fi
fi
#echo "$POST_DATA" > data.bin
IFS='=&'
set -- $POST_DATA
#2- Value1
#4- Value2
#6- Value3
#8- Value4
echo $2 $4 $6 $8
echo "Content-type: text/html"
echo ""
echo "<html><head><title>Saved</title></head><body>"
echo "Data received: $POST_DATA"
echo "</body></html>"
Hope this is of help for anybody.
Cheers
Actually I liked bolt's answer, so I made a version which works with Busybox as well (ash in Busybox does not support here string).
This code will accept key1 and key2 parameters, all others will be ignored.
while IFS= read -r -d '&' KEYVAL && [[ -n "$KEYVAL" ]]; do
case ${KEYVAL%=*} in
key1) KEY1=${KEYVAL#*=} ;;
key2) KEY2=${KEYVAL#*=} ;;
esac
done <<END
$(echo "${QUERY_STRING}&")
END
One can use the bash-cgi.sh, which processes :
the query string into the $QUERY_STRING_GET key and value array;
the post request data (x-www-form-urlencoded) into the $QUERY_STRING_POST key and value array;
the cookies data into the $HTTP_COOKIES key and value array.
Demands bash version 4.0 or higher (to define the key and value arrays above).
All processing is made by bash only (i.e. in an one process) without any external dependencies and additional processes invoking.
It has:
the check for max length of data, which can be transferred to it's input,
as well as processed as query string and cookies;
the redirect() procedure to produce redirect to itself with the extension changed to .html (it is useful for an one page's sites);
the http_header_tail() procedure to output the last two strings of the HTTP(S) respond's header;
the $REMOTE_ADDR value sanitizer from possible injections;
the parser and evaluator of the escaped UTF-8 symbols embedded into the values passed to the $QUERY_STRING_GET, $QUERY_STRING_POST and $HTTP_COOKIES;
the sanitizer of the $QUERY_STRING_GET, $QUERY_STRING_POST and $HTTP_COOKIES values against possible SQL injections (the escaping like the mysql_real_escape_string php function does, plus the escaping of # and $).
It is available here:
https://github.com/VladimirBelousov/fancy_scripts
This works in dash using for in loop
IFS='&'
for f in $query_string; do
value=${f##*=}
key=${f%%=*}
# if you need environment variable -> eval "qs_$key=$value"
done

Resources