bash if invalid arithmetic operator - bash

value1=ns1.abc.nl.
value2=ns2.abc.nl.
value3=ns3.abc.nl.
for domains in $(cat /home/carlito/Desktop/output.txt)
do
for nameserver in $(dig +short NS $domains)
do
if [[ ( $nameserver -eq $value1 ) || ( $nameserver -eq $value2 ) || ( $nameserve$
then
echo "$domains Namserver is local" >>/home/carlito/Desktop/resultaat.txt
break
else
echo "$domains namserver IS NOT local" >>/home/carlito/Desktop/resultaat.txt
fi
done
done
So i'm having trouble with the IF line of the script the error message=" nszero1.axc.nl.: syntax error: invalid arithmetic operator (error token is ".axc.nl."".
The purpose of this script= 'get output.txt (list of domains)' 'then dig +short NS $domains' and then check if its on my preferred name server $Value1,2,3 if true=good if false=bad
i already tried something like:
value="ns1.abc.nl./ns2.abc.nl/ns3.abc.nl."
for domains in $(cat /home/carlito/Desktop/output.txt)
do
if dig +short NS $domains == $value
then
echo "$domains is LOCAL"
else
echo"$domains Is NOT LOCAL"
fi
done
But what this does is if domain.nl=ns1.abc.nl./ns2.abc.nl it still echo's:Is NOT LOCAL. But i want the script to Echo=true if it has at least 1 of the values.
can some one point me in the right direction which function i should use and what am i doing wrong with the if line ?
thanks in advance

You are comparing a string with a numeric value. The syntax in the below if condition is to compare two numeric value and not for string comparison. Also your if condition is not properly ended with ]]. I thing you are comparing a string with a numeric value. Please see below correct if conditions:-
First a correct if condition if both the right and left hand values are numeric :-
if [[ ( $nameserver -eq $value1 ) || ( $nameserver -eq $value2 ) ]]
For correct string comparison:-
if [[ ( "$nameserver" == "$value1" ) || ( "$nameserver" == "$value2" ) ]]
Hope this will help you.

Related

Why my if/else statement doesn't work? (shell)

I have problem with this shell script:
current_time=$(date +"%T");
current_day_of_week="$(date +'%u')";
if [[ current_day_of_week == 1 ]];
then echo "Setting max time to 03:00:00" && max_time="03:00:00";
else echo "Setting max time to 01:30:00" && max_time="01:30:00"; fi;
I want to set variable max_time to 03:00:00 when it's monday, but the code doesn't work :(
The string "current_day_of_week" is not the same as the string "1". If you want to compare the value of the variable, you need to dereference it. Replace
if [[ current_day_of_week == 1 ]];
with
if [ "$current_day_of_week" = 1 ];
Since you're not getting a syntax error, we can assume you are using a shell that supports the [[ syntax (you should probably change the tag !) so you can use:
if [[ $current_day_of_week == 1 ]];
Note that although quoting variables is not strictly necessary inside the [[ construct, it is a good idea to use quotes anyway:
if [[ "$current_day_of_week" == 1 ]];

how to echo out associative part of associative array bash [duplicate]

Using:
set -o nounset
Having an indexed array like:
myArray=( "red" "black" "blue" )
What is the shortest way to check if element 1 is set?
I sometimes use the following:
test "${#myArray[#]}" -gt "1" && echo "1 exists" || echo "1 doesn't exist"
I would like to know if there's a preferred one.
How to deal with non-consecutive indexes?
myArray=()
myArray[12]="red"
myArray[51]="black"
myArray[129]="blue"
How to quick check that 51 is already set for example?
How to deal with associative arrays?
declare -A myArray
myArray["key1"]="red"
myArray["key2"]="black"
myArray["key3"]="blue"
How to quick check that key2 is already used for example?
To check if the element is set (applies to both indexed and associative array)
[ "${array[key]+abc}" ] && echo "exists"
Basically what ${array[key]+abc} does is
if array[key] is set, return abc
if array[key] is not set, return nothing
References:
See Parameter Expansion in Bash manual and the little note
if the colon is omitted, the operator tests only for existence [of parameter]
This answer is actually adapted from the answers for this SO question: How to tell if a string is not defined in a bash shell script?
A wrapper function:
exists(){
if [ "$2" != in ]; then
echo "Incorrect usage."
echo "Correct usage: exists {key} in {array}"
return
fi
eval '[ ${'$3'[$1]+muahaha} ]'
}
For example
if ! exists key in array; then echo "No such array element"; fi
From man bash, conditional expressions:
-v varname
True if the shell variable varname is set (has been assigned a value).
example:
declare -A foo
foo[bar]="this is bar"
foo[baz]=""
if [[ -v "foo[bar]" ]] ; then
echo "foo[bar] is set"
fi
if [[ -v "foo[baz]" ]] ; then
echo "foo[baz] is set"
fi
if [[ -v "foo[quux]" ]] ; then
echo "foo[quux] is set"
fi
This will show that both foo[bar] and foo[baz] are set (even though the latter is set to an empty value) and foo[quux] is not.
New answer
From version 4.2 of bash (and newer), there is a new -v option to built-in test command.
From version 4.3, this test could address element of arrays.
array=([12]="red" [51]="black" [129]="blue")
for i in 10 12 30 {50..52} {128..131};do
if [ -v 'array[i]' ];then
echo "Variable 'array[$i]' is defined"
else
echo "Variable 'array[$i]' not exist"
fi
done
Variable 'array[10]' not exist
Variable 'array[12]' is defined
Variable 'array[30]' not exist
Variable 'array[50]' not exist
Variable 'array[51]' is defined
Variable 'array[52]' not exist
Variable 'array[128]' not exist
Variable 'array[129]' is defined
Variable 'array[130]' not exist
Variable 'array[131]' not exist
Note: regarding ssc's comment, I've single quoted 'array[i]' in -v test, in order to satisfy shellcheck's error SC2208. This seem not really required here, because there is no glob character in array[i], anyway...
This work with associative arrays in same way:
declare -A aArray=([foo]="bar" [bar]="baz" [baz]=$'Hello world\041')
for i in alpha bar baz dummy foo test;do
if [ -v 'aArray[$i]' ];then
echo "Variable 'aArray[$i]' is defined"
else
echo "Variable 'aArray[$i]' not exist"
fi
done
Variable 'aArray[alpha]' not exist
Variable 'aArray[bar]' is defined
Variable 'aArray[baz]' is defined
Variable 'aArray[dummy]' not exist
Variable 'aArray[foo]' is defined
Variable 'aArray[test]' not exist
With a little difference:In regular arrays, variable between brackets ([i]) is integer, so dollar symbol ($) is not required, but for associative array, as key is a word, $ is required ([$i])!
Old answer for bash prior to V4.2
Unfortunately, bash give no way to make difference betwen empty and undefined variable.
But there is some ways:
$ array=()
$ array[12]="red"
$ array[51]="black"
$ array[129]="blue"
$ echo ${array[#]}
red black blue
$ echo ${!array[#]}
12 51 129
$ echo "${#array[#]}"
3
$ printf "%s\n" ${!array[#]}|grep -q ^51$ && echo 51 exist
51 exist
$ printf "%s\n" ${!array[#]}|grep -q ^52$ && echo 52 exist
(give no answer)
And for associative array, you could use the same:
$ unset array
$ declare -A array
$ array["key1"]="red"
$ array["key2"]="black"
$ array["key3"]="blue"
$ echo ${array[#]}
blue black red
$ echo ${!array[#]}
key3 key2 key1
$ echo ${#array[#]}
3
$ set | grep ^array=
array=([key3]="blue" [key2]="black" [key1]="red" )
$ printf "%s\n" ${!array[#]}|grep -q ^key2$ && echo key2 exist || echo key2 not exist
key2 exist
$ printf "%s\n" ${!array[#]}|grep -q ^key5$ && echo key5 exist || echo key5 not exist
key5 not exist
You could do the job without the need of externals tools (no printf|grep as pure bash), and why not, build checkIfExist() as a new bash function:
$ checkIfExist() {
eval 'local keys=${!'$1'[#]}';
eval "case '$2' in
${keys// /|}) return 0 ;;
* ) return 1 ;;
esac";
}
$ checkIfExist array key2 && echo exist || echo don\'t
exist
$ checkIfExist array key5 && echo exist || echo don\'t
don't
or even create a new getIfExist bash function that return the desired value and exit with false result-code if desired value not exist:
$ getIfExist() {
eval 'local keys=${!'$1'[#]}';
eval "case '$2' in
${keys// /|}) echo \${$1[$2]};return 0 ;;
* ) return 1 ;;
esac";
}
$ getIfExist array key1
red
$ echo $?
0
$ # now with an empty defined value
$ array["key4"]=""
$ getIfExist array key4
$ echo $?
0
$ getIfExist array key5
$ echo $?
1
What about a -n test and the :- operator?
For example, this script:
#!/usr/bin/env bash
set -e
set -u
declare -A sample
sample["ABC"]=2
sample["DEF"]=3
if [[ -n "${sample['ABC']:-}" ]]; then
echo "ABC is set"
fi
if [[ -n "${sample['DEF']:-}" ]]; then
echo "DEF is set"
fi
if [[ -n "${sample['GHI']:-}" ]]; then
echo "GHI is set"
fi
Prints:
ABC is set
DEF is set
tested in bash 4.3.39(1)-release
declare -A fmap
fmap['foo']="boo"
key='foo'
# should echo foo is set to 'boo'
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
key='blah'
# should echo blah is unset in fmap
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
Reiterating this from Thamme:
[[ ${array[key]+Y} ]] && echo Y || echo N
This tests if the variable/array element exists, including if it is set to a null value. This works with a wider range of bash versions than -v and doesn't appear sensitive to things like set -u. If you see a "bad array subscript" using this method please post an example.
This is the easiest way I found for scripts.
<search> is the string you want to find, ASSOC_ARRAY the name of the variable holding your associative array.
Dependign on what you want to achieve:
key exists:
if grep -qe "<search>" <(echo "${!ASSOC_ARRAY[#]}"); then echo key is present; fi
key exists not:
if ! grep -qe "<search>" <(echo "${!ASSOC_ARRAY[#]}"); then echo key not present; fi
value exists:
if grep -qe "<search>" <(echo "${ASSOC_ARRAY[#]}"); then echo value is present; fi
value exists not:
if ! grep -qe "<search>" <(echo "${ASSOC_ARRAY[#]}"); then echo value not present; fi
I wrote a function to check if a key exists in an array in Bash:
# Check if array key exists
# Usage: array_key_exists $array_name $key
# Returns: 0 = key exists, 1 = key does NOT exist
function array_key_exists() {
local _array_name="$1"
local _key="$2"
local _cmd='echo ${!'$_array_name'[#]}'
local _array_keys=($(eval $_cmd))
local _key_exists=$(echo " ${_array_keys[#]} " | grep " $_key " &>/dev/null; echo $?)
[[ "$_key_exists" = "0" ]] && return 0 || return 1
}
Example
declare -A my_array
my_array['foo']="bar"
if [[ "$(array_key_exists 'my_array' 'foo'; echo $?)" = "0" ]]; then
echo "OK"
else
echo "ERROR"
fi
Tested with GNU bash, version 4.1.5(1)-release (i486-pc-linux-gnu)
For all time people, once and for all.
There's a "clean code" long way, and there is a shorter, more concise, bash centered way.
$1 = The index or key you are looking for.
$2 = The array / map passed in by reference.
function hasKey ()
{
local -r needle="${1:?}"
local -nr haystack=${2:?}
for key in "${!haystack[#]}"; do
if [[ $key == $needle ]] ;
return 0
fi
done
return 1
}
A linear search can be replaced by a binary search, which would perform better with larger data sets. Simply count and sort the keys first, then do a classic binary halving of of the haystack as you get closer and closer to the answer.
Now, for the purist out there that is like "No, I want the more performant version because I may have to deal with large arrays in bash," lets look at a more bash centered solution, but one that maintains clean code and the flexibility to deal with arrays or maps.
function hasKey ()
{
local -r needle="${1:?}"
local -nr haystack=${2:?}
[ -n ${haystack["$needle"]+found} ]
}
The line [ -n ${haystack["$needle"]+found} ]uses the ${parameter+word} form of bash variable expansion, not the ${parameter:+word} form, which attempts to test the value of a key, too, which is not the matter at hand.
Usage
local -A person=(firstname Anthony lastname Rutledge)
if hasMapKey "firstname" person; then
# Do something
fi
When not performing substring expansion, using the form described
below (e.g., ‘:-’), Bash tests for a parameter that is unset or null.
Omitting the colon results in a test only for a parameter that is
unset. Put another way, if the colon is included, the operator tests
for both parameter’s existence and that its value is not null; if the
colon is omitted, the operator tests only for existence.
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
${parameter:=word}
If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional
parameters and special parameters may not be assigned to in this way.
${parameter:?word}
If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard
error and the shell, if it is not interactive, exits. Otherwise, the
value of parameter is substituted. ${parameter:+word}
If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameter-Expansion
If $needle does not exist expand to nothing, otherwise expand to the non-zero length string, "found". This will make the -n test succeed if the $needle in fact does exist (as I say "found"), and fail otherwise.
Both in the case of arrays and hash maps I find the easiest and more straightforward solution is to use the matching operator =~.
For arrays:
myArray=("red" "black" "blue")
if [[ " ${myArray[#]} " =~ " blue " ]]; then
echo "blue exists in myArray"
else
echo "blue does not exist in myArray"
fi
NOTE: The spaces around the array guarantee the first and last element can match. The spaces around the value guarantee an exact match.
For hash maps, it's actually the same solution since printing a hash map as a string gives you a list of its values.
declare -A myMap
myMap=(
["key1"]="red"
["key2"]="black"
["key3"]="blue"
)
if [[ " ${myMap[#]} " =~ " blue " ]]; then
echo "blue exists in myMap"
else
echo "blue does not exist in myMap"
fi
But what if you would like to check whether a key exists in a hash map? In the case you can use the ! operator which gives you the list of keys in a hash map.
if [[ " ${!myMap[#]} " =~ " key3 " ]]; then
echo "key3 exists in myMap"
else
echo "key3 does not exist in myMap"
fi
I get bad array subscript error when the key I'm checking is not set. So, I wrote a function that loops over the keys:
#!/usr/bin/env bash
declare -A helpList
function get_help(){
target="$1"
for key in "${!helpList[#]}";do
if [[ "$key" == "$target" ]];then
echo "${helpList["$target"]}"
return;
fi
done
}
targetValue="$(get_help command_name)"
if [[ -z "$targetvalue" ]];then
echo "command_name is not set"
fi
It echos the value when it is found & echos nothing when not found. All the other solutions I tried gave me that error.

shell, assigning value to variable from subscript

(using zsh with the following)
# i=for loop ((i;i<l,i++))
typeset -A _opts
AILABLE_ARG_OPTS=('v|verbose' 0 'd|destination' 1)
currentOpt=("${(#s/=/)${#[$i]##*-}}")
for k v in ${(kv)AVAILABLE_ARG_OPTS}; do
if (( $v != 0 )); then
_opts[${k#*|}]=$([[ -v currentOpt[2] ]] && #<---------- subscript
echo "${currentOpt[2]}" ||
echo "${#[$((++i))]}" # <---------------------------- HERE ?
)
fi
done
At 'HERE', $i using correct value but doesn't set increment for the loop.
- Did I miss something with the substitution ?
- Does the variable became readonly inner sub execution ?
edit add same style question..
_opts[${k#*|}]=${currentOpt[2]:-"${#[$((++i))]}"}
is it "normal" that the second statement is evaluate in all case ?
`$currentOpt[2]``setted or not
edit: (as comment answer)
_opts[${k#*|}]=$(
[[ -v currentOpt[2] ]] && #<---------- subscript
echo "${currentOpt[2]}" ||
echo "${#[$((++i))]}" # <--------------------- $i is not incremented
)
"~~equivalent~~" to (exept that increment..)
if [[ -v currentOpt[2] ]]; then
_opts[${k#*|}]="${currentOpt[2]}"
else
_opts[${k#*|}]="${#[$((++i))]}" # <-------- $i is incremented
fi
that the point I don't understand, in both case the right value is setted to _opts[$k].
But in the first exemple, $((++i)) don't assign it's value to$i
.. and i would know why
thanks for help

KShell regular expression comparision not working

I have the below if condition in my shell script which uses regular expressions. Basically I wanted to find out if $main contains string $pattrn.
main="$line1"
pattrn="$line"
if [[ $main = #($pattrn) ]];
then
echo $line>>/lawson/prod/work/errval
fi
Even If I have few matching values but this If condition in not returning anything.
Thanks in Advance
I think I have a solution, though there might be a more elegant solution with sed or awk.
#!/bin/ksh
variable1="ABCDEF"
variable2="CDE"
printf "variable1( %s )\n" $variable1
if( echo $variable1 | grep -q $variable2 ); then
printf "variable2 appears in variable1\n"
fi
variable3="GHI"
if( echo $variable1 | grep -q $variable3 ); then
printf "variable3 appears in variable1\n"
else
printf "variable3 does not appears in variable1\n"
fi
Output
variable1( ABCDEF )
variable2 appears in variable1
variable3 does not appears in variable1
Explanation: You are checking the return value of the command in parenthesis to ensure that the result is equal to 0. With the grep after the pipe, you are checking if grep found the string within the output string from the echo.
Hope that helps.
I wanted to find out if $main contains string $pattrn.
if [[ $main = #($pattrn) ]]
This tests for an exactly equal match. To test if $main contains $pattrn, we have to specify that there may be text before and after $pattrn:
if [[ $main = #(*$pattrn*) ]]
By the way, since we have just one pattern, we can do without #(…) as well:
if [[ $main = *$pattrn* ]]

bash, prompt for numerical input

d is an internal server lookup tool I use.
I am looking to allow a user to input any number between 0 (or 1) and 9999 (let's call this userinput) and have it display the result of:
d $userinput (e.g. 1234)
Then manipulate the results of that lookup (below gets rid of everything but the IP address to ping later):
grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'`
I know I need to use the while true; do read $blah etc etc. I am just not familiar with read enough to format it properly and more importantly:
get it to prompt for a numerical input between 0-9999
The other answers have many flaws, because they check that the user didn't input a number outside of the range they want. But what if a user enters something that is not a number? their strategy is broken from the start.
Instead it's better to let go only when we're sure that the user entered a number which lies within the wanted range.
while :; do
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] || continue
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) || continue
# Here I'm sure that number is a valid number in the range 0..9999
# So let's break the infinite loop!
break
done
The regex [[ $number =~ ^[[:digit:]]+$ ]] makes sure that the user only entered digits.
The clumsy (number=(10#$number)) part is here so that if the user enters a number that starts with a 0, bash would try to interpret it in radix 8 and we'd get a wrong result (e.g., if the user enters 010) and even an error in the case when a user enters, e.g., 09 (try it without this guard).
If you only want to prompt once and exit when the user inputs invalid terms, you have the logic:
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] || exit 1
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) || exit 1
# Here I'm sure that number is a valid number in the range 0..9999
If you want to explain to the user why the script exited, you can use a die function as:
die() {
(($#)) && printf >&2 '%s\n' "$#"
exit 1
}
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] ||
die '*** Error: you should have entered a number'
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) ||
die '*** Error, number not in range 0..9999'
# Here I'm sure that number is a valid number in the range 0..9999
<--edit-->
if all you want is the mechanic for prompting, try this:
echo -n "Enter server number:"
read userinput
then run validation checks on the input like this:
if [[ $userinput -lt 0 || $userinput -gt 9999 ]] # checks that the input is within the desired range
then
echo "Input outside acceptable range."
else
# insert your grep and ping stuff here
fi
<--end edit-->
on first read, i thought your problem sounded ideal for a wrapper script, so i was going to suggest this:
$ cat wrapper.sh
#!/usr/bin/bash
userinput=$1
if [[ $# != 1 ]] # checks that number of inputs is exactly one
then
echo "Too many inputs."
exit 2
elif [[ $userinput -lt 0 || $userinput -gt 9999 ]] # checks that the input is within the desired range
then
echo "Input outside acceptable range."
exit 3
fi
output=`d "$userinput"`
ping_address=`grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' <("$output")`
ping "$ping_address"
then call the script with like this:
$ wrapper.sh 1243
If you just want a number between two values, you can test their values:
read x
while [[ $x -lt 0 || $x -gt 9999 ]]; do
echo "bad value for x"
read x
done
echo "x=$x"
The read command doesn't prompt itself. Use a normal echo before to actually display a prompt. Use echo -n to not add a newline.

Resources