Performing IF Statement in awk - arrays

I have an array of users and I need to see if the owner of a file (logfile.log) exists within the array. Using awk I am able to pull the owner ($3) but when I use try to see if $3 is in user I get a syntax error at the beginning of my if statement. My limited understanding is that awk is not liking the syntax.
user=('michael' 'mark' 'luke' 'john' 'phil' 'sam' 'kevin');
ls -ldL logfile.log 2>/dev/null |
/bin/awk '{
Result = $NF ":\tPermissions=" $1;
if ([[ "${user[*]}" =~ (^|[^[:alpha:]])$3([^[:alpha:]]|$) ]]) {
Result = Result "\tOwner=SUPPORT";
}
else {
Result = Result "\tOwner=" $3;
}
print Result;
}'

Don't parse ls (http://mywiki.wooledge.org/ParsingLs). Use stat to get the owner (check your stat man page, there are different implementations of different OS's)
# give your arrays a plural variable name
users=('michael' 'mark' 'luke' 'john' 'phil' 'sam' 'kevin')
owner=$(stat -c '%U' logfile.log)
if [[ " ${users[*]} " == *" $owner "* ]]; then # spaces are deliberate
echo logfile.log has a valid owner: $owner
else
echo logfile.log is not owned by a valid user: $owner
fi
The other approach is to iterate over the array and look for an exact match:
valid=false
for user in "${users[#]}"; do
if [[ $user == $owner ]]; then
valid=true
break
fi
done
if $valid; then
echo file has a valid owner
fi
The main problem in your code is that you expect awk to understand bash syntax. It doesn't.

Related

bash access associative array creates unbound variable [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.

How to search if a value is present in array like variable using Bash script?

I have the below text, as the output of some command myscript.sh;
[
"string-1",
"string-2"
]
I have stored the output to some variable like below:
myarray=$(myscript.sh)
Now, I would like to echo value not present if the string string-3 is not present in the array, something like the code below;
value="string-3"
if [[ ! " ${myarray[*]} " =~ " ${value} " ]]; then
echo "value not present"
fi
This code will output value not present even if the value is present. What can be done to fix this issue?
The myarray variable is a string, and regular expressions can be used to determine whether it contains the specified substring.
myarray=([
"string-1",
"string-2"
])
value="string-3"
if [[ ! "$myarray" =~ .*"$value".* ]]; then
echo "value not present"
fi
Is the input a JSON array? If so, you should use a JSON-aware tool, like jq, to deal with it. Something like this:
if jq -e --arg value "$value" 'any(. == $value)' <<<"$myarray" >/dev/null; then
Explanation: --arg value "$value" copies the shell variable value into a jq variable with the same name. <<<"$myarray" passes the value of that variable as input (since it's not a bash array, the [*] is irrelevant). The filter any(. == $value) returns true if any array elements match $value, false otherwise. The -e option tells jq to use that result as its exit status, and >/dev/null discards the textual output. Since if uses the exit status of the command as its condition, that should be all you need.
Rewrite it like this:
if [[ ! $myarray =~ $value ]]; then
echo "value not present"
fi
Or like that:
[[ $myarray =~ $value ]] || echo "value not present"
Those "" spoiling everything.

echo a variable and then grep to see if value exist in a file is not returning anything. Unix Shell Scripting

I'm trying to figure out how to determine if a variable contains a value from a file using grep, this is not returning anything, so I'm going to explain it.
I have my code that is this:
MyFiles="MyFile-I-20160606_141_Employees.txt"
DirFiles="/dev/fs/C/Users/salasfri/Desktop/MyFiles.txt"
for OutFile in $(cat $DirFiles); do
if [[ $( echo $MyFiles | grep -c $OutFile ) -gt 0 ]]; then
print "The file $OutFile exist!!"
fi
done
and the file in /dev/fs/C/Users/salasfri/Desktop/MyFiles.txt contains the following values:
MyFile-I-*_141_Employees.txt
MyFile-I-*_141_Products.txt
MyFile-I-*_141_Deparments.txt
the idea is verify if the variable "MyFiles" is found in the MyFiles.txt file, as you can see is using the pattern "*" due that is a date, it will change.
that solutions is not returning any count of files, there's something that I'm doing wrong?
You can try to change the searchstring before searching.
An example with three teststrings:
for teststring in MyFile-I-20160606_141_Employees.txt MyFile-I-20160606_142_Employees.txt MyFile-I-20160606_141_Others.txt
do
grepstr=$(sed 's/[0-9]\{8\}_/*_/' <<< "${teststring}")
fgrep "${grepstr}" "${DirFiles}"
found=$(fgrep "${grepstr}" "${DirFiles}")
if [ $? -eq 0 ]; then
echo "${found} matches ${teststring}."
fi
done
In your case you can make the code shorter with
fgrep -q "$(sed 's/[0-9]\{8\}_/*_/' <<< "${MyFiles}")" $DirFiles &&
echo "The file $(sed 's/[0-9]\{8\}_/*_/' <<< "${MyFiles}") exist!!"
Your patterns are glob-style patterns, not regular expressions. The pattern abc-*_X.txt will not match the string abc-1234_X.txt.
You want to use a shell construct that does glob matching.
MyFiles="MyFile-I-20160606_141_Employees.txt"
sed 's/\r$//' "/dev/fs/C/Users/salasfri/Desktop/MyFiles.txt" \
| while IFS= read -r Pattern; do
if [[ $MyFiles == $Pattern ]]; then
print "$MyFiles matches pattern $Pattern"
break
fi
done

Searching a string, output it to an array then delete it based on user input

So I'm developing a simple Address book and its going well.
It uses a singular text file to store all its information.
At the minute i can searh the file for a specific string, not a problem.
I'm pretty sure i can get it to delete a string based on a users input, thats kind of easy but i thought for extra snazziness it would be
a neat idea if I could grab the output of the search term - a multi-line output say - and store each new line into an array, each assigned a number.
From there the user could specify they would like to delete the line based on their input.
So far this is the 'delete' script:
ADDRESSBOOK=~/Documents/Address-Book/add_data.txt
export ADDRESSBOOK
echo "=============================== Delete a Record ==============================="
echo "==============================================================================="
echo "============ Search A Record, then delete it! Type 'exit' to exit ============="
exit=0
search(){
while [ $exit -ne 1 ]
do
echo "Find something for me to delete"
echo -n "-----------------------> "
read searchTerm
if [ "$searchTerm" = "exit" ]
then
exit=1
else
searchOutput=$(grep $searchTerm*.$ $ADDRESSBOOK | sort)
echo "$searchOutput"
fi
done
exit 0
}
search
The address book automatically adds each record with /n so it displays each record on a new line anyway.
Is it possible to take the $searchRecord variable and pass that to an array?
And before I go wrapping my head around arrays, is this even the best approach to take for what I'm trying to do?
I am currently trying something along the lines of
if [ "$searchTerm" = "exit" ]
then
exit=1
else
searchOutput=$(grep $searchTerm*.$ $ADDRESSBOOK | sort)
echo {$searchOutput[*] '/n' }
fi
the '/n' is to TRY and get the records to each appear on a new line. Any advice guys?
Try something like:
l=$(grep $searchTerm $ADDRESSBOOK | \
sort | { \
n=0; \
while read a; \
do echo "searchOutput[$n]=\"$a\";" ; \
n=$((n + 1)) ; \
done; \
})
eval $l
for line in "${searchOutput[#]}"
do
echo $line
done
while read -p 'Find something for me to delete> ' searchTerm
[[ $searchTerm != 'exit' ]] do readarray -t searchOutput < <(grep -i -w "$searchTerm" "$ADDRESSBOOK" | sort)
if ((${#searchOutput[#]}>0)); then
PS3="Select line to delete: "
select line in "${searchOutput[#]}"; do
[[ -z $line ]] && break
line=$(fgrep -nx "$line" "$ADDRESSBOOK")
sed -i ${line%%:*}d "$ADDRESSBOOK"
echo "Deleting line" $line
done
elif [ $searchTerm = "exit" ]
then
exit=1
else
echo "------------- Could Not Find > " $searchTerm
exit=1
fi
done
exit 0
}

Check multiple var if exists in array with grep

I am using this code to check one $var if exists in array :
if echo ${myArr[#]} | grep -qw $myVar; then echo "Var exists on array" fi
How could I combine more than one $vars to my check? Something like grep -qw $var1,$var2; then ... fi
Thank you in Advance.
if echo ${myArr[#]} | grep -qw -e "$myVar" -e "$otherVar"
then
echo "Var exists on array"
fi
From the man-page:
-e PATTERN, --regexp=PATTERN
Use PATTERN as the pattern.
This can be used to specify multiple search patterns, or to protect a pattern beginning with a hyphen (-). (-e is specified by POSIX.)
But if you want to use arrays like this you might as well use the bash built-in associative arrays.
To implement and logic:
myVar1=home1
myVar2=home2
myArr[0]=home1
myArr[1]=home2
if echo ${myArr[#]} | grep -qw -e "$myVar1.*$myVar2" -e "$myVar2.*$myVar1"
then
echo "Var exists on array"
fi
# using associative arrays
declare -A assoc
assoc[home1]=1
assoc[home2]=1
if [[ ${assoc[$myVar1]} && ${assoc[$myVar2]} ]]; then
echo "Var exists on array"
fi
Actually you don't need grep for this, Bash is perfectly capable of doing Extended Regular Expressions itself (Bash 3.0 or later).
pattern="$var1|$var2|$var3"
for element in "${myArr[#]}"
do
if [[ $element =~ $pattern ]]
then
echo "$pattern exists in array"
break
fi
done
Something quadratic, but aware of spaces:
myArr=(aa "bb c" ddd)
has_values(){
for e in "${myArr[#]}" ; do
for f ; do
if [ "$e" = "$f" ]; then return 0 ; fi
done
done
return 1
}
if has_values "ee" "bb c" ; then echo yes ; else echo "no" ; fi
this example will print no because "bb c" != "bb c"

Resources