How to check if array values are empty, in bulk? - arrays

I want a script bash/sh/ksh which compare a lot of variables, maybe in an array, and tell me if variable is empty or not.
I think something like this, it but doesn't work.
ARRAY=(
bash="yes"
cash=""
trash="no"
empty=""
)
for var in "${ARRAY[#]}"; do
if [ "$var" == "$empty" ]
then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
I want an output like this
bash is not empty
cash is empty...

If you're willing to limit your runtime environment to a recent version of bash (or modify the code to support ksh93's equivalent syntax),
#!/bin/bash
# ^^^^ -- specifically, bash 4.0 or newer
declare -A array # associative arrays need to be declared!
array=( [bash]="yes" [cash]="" [trash]="no" [empty]="" )
for idx in "${!array[#]}"; do
if [[ ${array[$idx]} ]]; then
echo "$idx is not empty"
else
echo "$idx is empty"
fi
done
To iterate over keys in an array, as opposed to values, the syntax is "${!array[#]}", as opposed to "${array[#]}"; if you merely iterate over the values, you don't know the name of the one currently being evaluated.
Alternately, let's say we aren't going to use an array at all; another way to set a namespace for variables you intend to be able to treat in a similar manner is by prefixing them:
#!/bin/bash
val_bash=yes
val_cash=
val_trash=no
val_empty=
for var in "${!val_#}"; do
if [[ ${!var} ]]; then
echo "${var#val_} is not empty"
else
echo "${var#val_} is empty"
fi
done
This works (on bash 3.x as well) because "${!prefix#}" expands to the list of variable names starting with prefix, and "${!varname}" expands to the contents of a variable whose name is itself stored in the variable varname.

Iterate over the array elements, and inside the loop for read set IFS as = to get variable and it's value in two separate variables, then check if the value is empty:
for i in "${array[#]}"; do
IFS== read var value <<<"$i"
if [ -z "$value" ]; then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
Outputs:
bash is not empty
cash is empty
trash is not empty
empty is empty

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.

Bash: Check if key exists in associative array

Goal:
All I want to do is to check if a USER_PARAMETERS_KEYs[j] exists in RPM_PARAMETERS_HASH
associative array. I have one array and one associative arrays as the following:
Predefined:
declare -a USER_PARAMETERS_KEYS='([0]="userField" [1]="fUserField" [2]="srcIPField" [3]="srcPortField" [4]="dstIPField" [5]="dstPortField" [6]="dateField" [7]="timeField")'
declare -A RPM_PARAMETERS_HASH='([timeField]="Time" [userField]="User-Name" [dstIPField]="" [srcIPField]="Source-IP-Address" [dstPortField]="Target-UDP-Port" [fUserField]="Full-Name" [dateField]="Date" [srcPortField]="Source-UDP-Port" )'
I implemented the following:
if [[ ${RPM_PARAMETERS_HASH[${USER_PARAMETERS_KEYS[j]}]} ]] ; then
Problem
My problem is when ${USER_PARAMETERS_KEYS[j]} becomes equal to dstIPField since it has an empty string value in the associative array, so the if condition is not satisfied although the key is there.
With bash you can just use -v option to [[:
[[ -v RPM_PARAMETERS_HASH["${USER_PARAMETERS_KEYS[j]}"] ]]
#sailnfool - kudos to You for finding this solution. The "-v" solution doesn't work for me.
Regarding "+_" - in fact the "operator" is the "+" sign, as states in a "Shell Parameter Expansion" in the Bash Reference Manual:
"${parameter:+word}
If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted."
And in the introduction: "Omitting the colon results in a test only for a parameter that is unset."
$ declare -A arr; arr["a"]="x"
$ echo ${arr[a]+_}
_
$ echo ${arr[a]+1}
1
$ echo "|${arr[c]+1}|"
||
$ arr["c"]=''
$ [[ ${arr[c]+1} ]] && echo "ok" || echo "nok"
ok
$ [[ ${arr[c]:+1} ]] && echo "ok" || echo "nok"
nok
Posting an answer here to add a bit of clarification to answers from #acan and #sailnfool. I found that I needed to run through some example scenarios to full grasp the nuances of the method they both describe (thanks to #sailnfool for the example script you created in your answer). At any rate, my comments here would not have been very legible if I simply replied to #sailnfool's post with a comment, so here goes FBO.
Clarifying the logic/outcomes of #sailnfool's script/answer:
If an array is defined, but a specific element (key) within the array has never been defined: both queries return a result of, "array element does not exist"
If an array element's value previously existed and has been unset: both queries return "array element does not exist"
If an array element's (key's) value is not NULL: both queries return "array element exists"
If an array element's value is NULL: 1st query returns "array element does exist"
If an array element's value is NULL: 2nd query returns "array element does not exist"
I couldn't quite tell from the original question whether the poster was attempting to derive whether or not a particular array element existed (i.e. whether the specific array element had been declared or not) versus whether or not a particular array element had a value (not null).
If the intent is to determine whether or not a specific array element has ever been set (including setting it equal to NULL), then use this method:
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
For example:
declare -A arr
arr["c"]=""
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
or
declare -A arr
arr["c"]=''
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
If a user's intent is to detect when an array element existed and was subsequently unset, or when a specific array element has never been created, either of the two branching methods are going to return the same result. Namely, that the element does not exist. So, re-capping... the only difference between them is the ability of the first method (examples shown above) to branch when an array element exists but is set equal to NULL. While the 2nd branching method will only return a true path (array element exists) when the array element has been defined, and contains a non-null value.
Bottom Line
Your need scenario 1: tell when an array element is defined and = any value, including NULL
Use: 1st branch method
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
Your need scenario 2: tell me when an array element is defined and contains a non-NULL value
Use: either logic branching method
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
or
[[ ${arr[c]:+1} ]] && echo "array key exists" || echo "array key does not exist"
Your need scenario 3: tell me when an array element is not defined at all (i.e. never defined or was defined and subsequently unset)
Use: either logic branching method
[[ ${arr[c]+1} ]] && echo "array key exists" || echo "array key does not exist"
or
[[ ${arr[c]:+1} ]] && echo "array key exists" || echo "array key does not exist"
In the linuxhint article: "Associative Arrays Bash Examples"1 Under Example #5 it shows:
$ if [ ${ArrayName[searchKEY]+_} ]; then echo "Exists"; else echo "Not available"; fi
Note that the "searchKEY" could either be a literal value or a variable that expands to a literal value. Kudos to the author of the article that uncovered this "+_" operator on shell variables as a way to test for the key existence. I was unable to tease this out of the GNU BASH Reference Manual2. I am sure it is related to parameter expansion but I never realized that it would show associative (hash)
array member existence.
It looks like you can make it work by splitting up the steps (first evaluate the key, then use the -v test):
declare -a USER_PARAMETERS_KEYS='([0]="userField" [1]="fUserField" [2]="srcIPField" [3]="srcPortField" [4]="dstIPField" [5]="dstPortField" [6]="dateField" [7]="timeField")'
declare -A RPM_PARAMETERS_HASH='([timeField]="Time" [userField]="User-Name" [dstIPField]="" [srcIPField]="Source-IP-Address" [dstPortField]="Target-UDP-Port" [fUserField]="Full-Name" [dateField]="Date" [srcPortField]="Source-UDP-Port" )'
j=4
key=${USER_PARAMETERS_KEYS[j]}
[[ -v RPM_PARAMETERS_HASH[$key] ]] && echo yes # output: yes

How do you unset all empty array elements in bash? [duplicate]

I need to remove an element from an array in bash shell.
Generally I'd simply do:
array=("${(#)array:#<element to remove>}")
Unfortunately the element I want to remove is a variable so I can't use the previous command.
Down here an example:
array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[#]/$delete} ) -> but clearly doesn't work because of {}
Any idea?
The following works as you would like in bash and zsh:
$ array=(pluto pippo)
$ delete=pluto
$ echo ${array[#]/$delete}
pippo
$ array=( "${array[#]/$delete}" ) #Quotes when working with strings
If need to delete more than one element:
...
$ delete=(pluto pippo)
for del in ${delete[#]}
do
array=("${array[#]/$del}") #Quotes when working with strings
done
Caveat
This technique actually removes prefixes matching $delete from the elements, not necessarily whole elements.
Update
To really remove an exact item, you need to walk through the array, comparing the target to each element, and using unset to delete an exact match.
array=(pluto pippo bob)
delete=(pippo)
for target in "${delete[#]}"; do
for i in "${!array[#]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
Note that if you do this, and one or more elements is removed, the indices will no longer be a continuous sequence of integers.
$ declare -p array
declare -a array=([0]="pluto" [2]="bob")
The simple fact is, arrays were not designed for use as mutable data structures. They are primarily used for storing lists of items in a single variable without needing to waste a character as a delimiter (e.g., to store a list of strings which can contain whitespace).
If gaps are a problem, then you need to rebuild the array to fill the gaps:
for i in "${!array[#]}"; do
new_array+=( "${array[i]}" )
done
array=("${new_array[#]}")
unset new_array
You could build up a new array without the undesired element, then assign it back to the old array. This works in bash:
array=(pluto pippo)
new_array=()
for value in "${array[#]}"
do
[[ $value != pluto ]] && new_array+=($value)
done
array=("${new_array[#]}")
unset new_array
This yields:
echo "${array[#]}"
pippo
This is the most direct way to unset a value if you know it's position.
$ array=(one two three)
$ echo ${#array[#]}
3
$ unset 'array[1]'
$ echo ${array[#]}
one three
$ echo ${#array[#]}
2
This answer is specific to the case of deleting multiple values from large arrays, where performance is important.
The most voted solutions are (1) pattern substitution on an array, or (2) iterating over the array elements. The first is fast, but can only deal with elements that have distinct prefix, the second has O(n*k), n=array size, k=elements to remove. Associative array are relative new feature, and might not have been common when the question was originally posted.
For the exact match case, with large n and k, possible to improve performance from O(nk) to O(n+klog(k)). In practice, O(n) assuming k much lower than n. Most of the speed up is based on using associative array to identify items to be removed.
Performance (n-array size, k-values to delete). Performance measure seconds of user time
N K New(seconds) Current(seconds) Speedup
1000 10 0.005 0.033 6X
10000 10 0.070 0.348 5X
10000 20 0.070 0.656 9X
10000 1 0.043 0.050 -7%
As expected, the current solution is linear to N*K, and the fast solution is practically linear to K, with much lower constant. The fast solution is slightly slower vs the current solution when k=1, due to additional setup.
The 'Fast' solution: array=list of input, delete=list of values to remove.
declare -A delk
for del in "${delete[#]}" ; do delk[$del]=1 ; done
# Tag items to remove, based on
for k in "${!array[#]}" ; do
[ "${delk[${array[$k]}]-}" ] && unset 'array[k]'
done
# Compaction
array=("${array[#]}")
Benchmarked against current solution, from the most-voted answer.
for target in "${delete[#]}"; do
for i in "${!array[#]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
array=("${array[#]}")
Here's a one-line solution with mapfile:
$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[#]}" | grep -Pzv "<regexp>")
Example:
$ arr=("Adam" "Bob" "Claire"$'\n'"Smith" "David" "Eve" "Fred")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 6 Contents: Adam Bob Claire
Smith David Eve Fred
$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[#]}" | grep -Pzv "^Claire\nSmith$")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 5 Contents: Adam Bob David Eve Fred
This method allows for great flexibility by modifying/exchanging the grep command and doesn't leave any empty strings in the array.
Partial answer only
To delete the first item in the array
unset 'array[0]'
To delete the last item in the array
unset 'array[-1]'
To expand on the above answers, the following can be used to remove multiple elements from an array, without partial matching:
ARRAY=(one two onetwo three four threefour "one six")
TO_REMOVE=(one four)
TEMP_ARRAY=()
for pkg in "${ARRAY[#]}"; do
for remove in "${TO_REMOVE[#]}"; do
KEEP=true
if [[ ${pkg} == ${remove} ]]; then
KEEP=false
break
fi
done
if ${KEEP}; then
TEMP_ARRAY+=(${pkg})
fi
done
ARRAY=("${TEMP_ARRAY[#]}")
unset TEMP_ARRAY
This will result in an array containing:
(two onetwo three threefour "one six")
Here's a (probably very bash-specific) little function involving bash variable indirection and unset; it's a general solution that does not involve text substitution or discarding empty elements and has no problems with quoting/whitespace etc.
delete_ary_elmt() {
local word=$1 # the element to search for & delete
local aryref="$2[#]" # a necessary step since '${!$2[#]}' is a syntax error
local arycopy=("${!aryref}") # create a copy of the input array
local status=1
for (( i = ${#arycopy[#]} - 1; i >= 0; i-- )); do # iterate over indices backwards
elmt=${arycopy[$i]}
[[ $elmt == $word ]] && unset "$2[$i]" && status=0 # unset matching elmts in orig. ary
done
return $status # return 0 if something was deleted; 1 if not
}
array=(a 0 0 b 0 0 0 c 0 d e 0 0 0)
delete_ary_elmt 0 array
for e in "${array[#]}"; do
echo "$e"
done
# prints "a" "b" "c" "d" in lines
Use it like delete_ary_elmt ELEMENT ARRAYNAME without any $ sigil. Switch the == $word for == $word* for prefix matches; use ${elmt,,} == ${word,,} for case-insensitive matches; etc., whatever bash [[ supports.
It works by determining the indices of the input array and iterating over them backwards (so deleting elements doesn't screw up iteration order). To get the indices you need to access the input array by name, which can be done via bash variable indirection x=1; varname=x; echo ${!varname} # prints "1".
You can't access arrays by name like aryname=a; echo "${$aryname[#]}, this gives you an error. You can't do aryname=a; echo "${!aryname[#]}", this gives you the indices of the variable aryname (although it is not an array). What DOES work is aryref="a[#]"; echo "${!aryref}", which will print the elements of the array a, preserving shell-word quoting and whitespace exactly like echo "${a[#]}". But this only works for printing the elements of an array, not for printing its length or indices (aryref="!a[#]" or aryref="#a[#]" or "${!!aryref}" or "${#!aryref}", they all fail).
So I copy the original array by its name via bash indirection and get the indices from the copy. To iterate over the indices in reverse I use a C-style for loop. I could also do it by accessing the indices via ${!arycopy[#]} and reversing them with tac, which is a cat that turns around the input line order.
A function solution without variable indirection would probably have to involve eval, which may or may not be safe to use in that situation (I can't tell).
Using unset
To remove an element at particular index, we can use unset and then do copy to another array. Only just unset is not required in this case. Because unset does not remove the element it just sets null string to the particular index in array.
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
unset 'arr[1]'
declare -a arr2=()
i=0
for element in "${arr[#]}"
do
arr2[$i]=$element
((++i))
done
echo "${arr[#]}"
echo "1st val is ${arr[1]}, 2nd val is ${arr[2]}"
echo "${arr2[#]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
Output is
aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd
Using :<idx>
We can remove some set of elements using :<idx> also. For example if we want to remove 1st element we can use :1 as mentioned below.
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[#]:1}")
echo "${arr2[#]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
Output is
bb cc dd ee
1st val is cc, 2nd val is dd
http://wiki.bash-hackers.org/syntax/pe#substring_removal
${PARAMETER#PATTERN} # remove from beginning
${PARAMETER##PATTERN} # remove from the beginning, greedy match
${PARAMETER%PATTERN} # remove from the end
${PARAMETER%%PATTERN} # remove from the end, greedy match
In order to do a full remove element, you have to do an unset command with an if statement. If you don't care about removing prefixes from other variables or about supporting whitespace in the array, then you can just drop the quotes and forget about for loops.
See example below for a few different ways to clean up an array.
options=("foo" "bar" "foo" "foobar" "foo bar" "bars" "bar")
# remove bar from the start of each element
options=("${options[#]/#"bar"}")
# options=("foo" "" "foo" "foobar" "foo bar" "s" "")
# remove the complete string "foo" in a for loop
count=${#options[#]}
for ((i = 0; i < count; i++)); do
if [ "${options[i]}" = "foo" ] ; then
unset 'options[i]'
fi
done
# options=( "" "foobar" "foo bar" "s" "")
# remove empty options
# note the count variable can't be recalculated easily on a sparse array
for ((i = 0; i < count; i++)); do
# echo "Element $i: '${options[i]}'"
if [ -z "${options[i]}" ] ; then
unset 'options[i]'
fi
done
# options=("foobar" "foo bar" "s")
# list them with select
echo "Choose an option:"
PS3='Option? '
select i in "${options[#]}" Quit
do
case $i in
Quit) break ;;
*) echo "You selected \"$i\"" ;;
esac
done
Output
Choose an option:
1) foobar
2) foo bar
3) s
4) Quit
Option?
Hope that helps.
There is also this syntax, e.g. if you want to delete the 2nd element :
array=("${array[#]:0:1}" "${array[#]:2}")
which is in fact the concatenation of 2 tabs. The first from the index 0 to the index 1 (exclusive) and the 2nd from the index 2 to the end.
POSIX shell script does not have arrays.
So most probably you are using a specific dialect such as bash, korn shells or zsh.
Therefore, your question as of now cannot be answered.
Maybe this works for you:
unset array[$delete]
What I do is:
array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"
BAM, that item is removed.
This is a quick-and-dirty solution that will work in simple cases but will break if (a) there are regex special characters in $delete, or (b) there are any spaces at all in any items. Starting with:
array+=(pluto)
array+=(pippo)
delete=(pluto)
Delete all entries exactly matching $delete:
array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)
resulting in
echo $array -> pippo, and making sure it's an array:
echo $array[1] -> pippo
fmt is a little obscure: fmt -1 wraps at the first column (to put each item on its own line. That's where the problem arises with items in spaces.) fmt -999999 unwraps it back to one line, putting back the spaces between items. There are other ways to do that, such as xargs.
Addendum: If you want to delete just the first match, use sed, as described here:
array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
Actually, I just noticed that the shell syntax somewhat has a behavior built-in that allows for easy reconstruction of the array when, as posed in the question, an item should be removed.
# let's set up an array of items to consume:
x=()
for (( i=0; i<10; i++ )); do
x+=("$i")
done
# here, we consume that array:
while (( ${#x[#]} )); do
i=$(( $RANDOM % ${#x[#]} ))
echo "${x[i]} / ${x[#]}"
x=("${x[#]:0:i}" "${x[#]:i+1}")
done
Notice how we constructed the array using bash's x+=() syntax?
You could actually add more than one item with that, the content of a whole other array at once.
In ZSH this is dead easy (note this uses more bash compatible syntax than necessary where possible for ease of understanding):
# I always include an edge case to make sure each element
# is not being word split.
start=(one two three 'four 4' five)
work=(${(#)start})
idx=2
val=${work[idx]}
# How to remove a single element easily.
# Also works for associative arrays (at least in zsh)
work[$idx]=()
echo "Array size went down by one: "
[[ $#work -eq $(($#start - 1)) ]] && echo "OK"
echo "Array item "$val" is now gone: "
[[ -z ${work[(r)$val]} ]] && echo OK
echo "Array contents are as expected: "
wanted=("${start[#]:0:1}" "${start[#]:2}")
[[ "${(j.:.)wanted[#]}" == "${(j.:.)work[#]}" ]] && echo "OK"
echo "-- array contents: start --"
print -l -r -- "-- $#start elements" ${(#)start}
echo "-- array contents: work --"
print -l -r -- "-- $#work elements" "${work[#]}"
Results:
Array size went down by one:
OK
Array item two is now gone:
OK
Array contents are as expected:
OK
-- array contents: start --
-- 5 elements
one
two
three
four 4
five
-- array contents: work --
-- 4 elements
one
three
four 4
five
To avoid conflicts with array index using unset - see https://stackoverflow.com/a/49626928/3223785 and https://stackoverflow.com/a/47798640/3223785 for more information - reassign the array to itself: ARRAY_VAR=(${ARRAY_VAR[#]}).
#!/bin/bash
ARRAY_VAR=(0 1 2 3 4 5 6 7 8 9)
unset ARRAY_VAR[5]
unset ARRAY_VAR[4]
ARRAY_VAR=(${ARRAY_VAR[#]})
echo ${ARRAY_VAR[#]}
A_LENGTH=${#ARRAY_VAR[*]}
for (( i=0; i<=$(( $A_LENGTH -1 )); i++ )) ; do
echo ""
echo "INDEX - $i"
echo "VALUE - ${ARRAY_VAR[$i]}"
done
exit 0
[Ref.: https://tecadmin.net/working-with-array-bash-script/ ]
How about something like:
array=(one two three)
array_t=" ${array[#]} "
delete=one
array=(${array_t// $delete / })
unset array_t
#/bin/bash
echo "# define array with six elements"
arr=(zero one two three 'four 4' five)
echo "# unset by index: 0"
unset -v 'arr[0]'
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
arr_delete_by_content() { # value to delete
for i in ${!arr[*]}; do
[ "${arr[$i]}" = "$1" ] && unset -v 'arr[$i]'
done
}
echo "# unset in global variable where value: three"
arr_delete_by_content three
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
echo "# rearrange indices"
arr=( "${arr[#]}" )
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
delete_value() { # value arrayelements..., returns array decl.
local e val=$1; new=(); shift
for e in "${#}"; do [ "$val" != "$e" ] && new+=("$e"); done
declare -p new|sed 's,^[^=]*=,,'
}
echo "# new array without value: two"
declare -a arr="$(delete_value two "${arr[#]}")"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
delete_values() { # arraydecl values..., returns array decl. (keeps indices)
declare -a arr="$1"; local i v; shift
for v in "${#}"; do
for i in ${!arr[*]}; do
[ "$v" = "${arr[$i]}" ] && unset -v 'arr[$i]'
done
done
declare -p arr|sed 's,^[^=]*=,,'
}
echo "# new array without values: one five (keep indices)"
declare -a arr="$(delete_values "$(declare -p arr|sed 's,^[^=]*=,,')" one five)"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
# new array without multiple values and rearranged indices is left to the reader

Checking if variable starts with another variable in bash

Similar to this >> In bash, how can I check if a string begins with some value?, but not duplicated.
I have two arrays, and for each string inside the first one, I wanna check whether the strings in the second one start with the string from the first one or not.
array1=("test1","test2","test3");
array2=("test1 etc","test1 nanana","test2 zzz","test3 abracadabra");
for i in "${!array1[#]}"; do
for j in "${!array2[#]}"; do
if [[ "${array1[i]}" == "${array2[j]}*" ]]; then
echo "array1[$i] and arry2[$j] initial matches!";
fi;
done;
done
I tried many conditions inside if, such as:
if [[ "${array1[i]}" == "${array2[j]*}" ]]
if [[ "${array1[i]}" == "${array2[j]}*" ]]
if [[ "${array1[i]}" = "${array2[j]*}" ]]
if [[ "${array1[i]}" = "${array2[j]}*" ]]
Also without quotes, braces, and so on, all with no success.
There are some errors in your code, first of all, array declaration in bash: if you don't put spaces you have only one element. Remember to always print the variables before trying anything else on them.
From the bash docs:
ARRAY=(value1 value2 ... valueN)
Each value is then in the form of [indexnumber=]string. The index
number is optional. If it is supplied, that index is assigned to it;
otherwise the index of the element assigned is the number of the last
index that was assigned, plus one. This format is accepted by declare
as well. If no index numbers are supplied, indexing starts at zero.
To cycle array elements:
for element in "${array[#]}"
do
echo "$element"
done
Here is a code snippet:
array1=(test1 test2 test3);
array2=(test1 etc "test1 nanana" test2zzz test3 abracadabra);
for word1 in "${array1[#]}"; do
for word2 in "${array2[#]}"; do
echo "w1=$word1, w2=$word2"
if [[ ${word2} == ${word1}* ]]; then
echo "$word1 and $word2 initial matches!";
fi;
done;
done
After the comment by the OP I realised that He was trying to use indices, to do that you have to use the "$" also for the indices "i" and "j".
Here is a working solution:
for i in "${!array1[#]}"; do
for j in "${!array2[#]}"; do
echo "${array1[$i]} ${array2[$j]}"
if [[ ${array2[$j]} == ${array1[$i]}* ]]; then
echo "$array1[$i] and $array2[$j] initial matches!";
fi;
done;
done

Deleting specific values from an array in ksh

I have a customized .profile that I use in ksh and below is a function that I created to skip back and forth from directories with overly complicated or long names.
As you can see, the pathnames are stored in an array (BOOKMARKS[]) to keep track of them and reference them at a later time. I want to be able to delete certain values from the array, using a case statement (or OPTARG if necessary) so that I can just type bmk -d # to remove the path at the associated index.
I have fiddled around with array +A and -A, but it just wound up screwing up my array (what is left in the commented out code may not be pretty...I didn't proofread it).
Any suggestions/tips on how to create that functionality? Thanks!
# To bookmark the current directory you are in for easy navigation back and forth from multiple non-aliased directories
# Use like 'bmk' (sets the current directory to a bookmark number) to go back to this directory, i.e. type 'bmk 3' (for the 3rd)
# To find out what directories are linked to which numbers, type 'bmk -l' (lowercase L)
# For every new directory bookmarked, the number will increase so the first time you run 'bmk' it will be 1 then 2,3,4...etc. for every consecutive run therea
fter
# TODO: finish -d (delete bookmark entry) function
make_bookmark()
{
if [[ $# -eq 0 ]]; then
BOOKMARKS[${COUNTER}]=${PWD}
(( COUNTER=COUNTER+1 ))
else
case $1 in
-l) NUM_OF_ELEMENTS=${#BOOKMARKS[*]}
while [[ ${COUNTER} -lt ${NUM_OF_ELEMENTS} ]]
do
(( ACTUAL_NUM=i+1 ))
echo ${ACTUAL_NUM}":"${BOOKMARKS[${i}]}
(( COUNTER=COUNTER+1 ))
done
break ;;
#-d) ACTUAL_NUM=$2
#(( REMOVE=${ACTUAL_NUM}-1 ))
#echo "Removing path ${BOOKMARKS[${REMOVE}]} from 'bmk'..."
#NUM_OF_ELEMENTS=${#BOOKMARKS[*]}
#while [[ ${NUM_OF_ELEMENTS} -gt 0 ]]
#do
#if [[ ${NUM_OF_ELEMENTS} -ne ${ACTUAL_NUM} ]]; then
# TEMP_ARR=$(echo "${BOOKMARKS[*]}")
# (( NUM_OF_ELEMENTS=${NUM_OF_ELEMENTS}-1 ))
#fi
#echo $TEMP_ARR
#done
#break
#for VALUE in ${TEMP_ARR}
#do
# set +A BOOKMARK ${TEMP_ARR}
#done
#echo ${BOOKMARK[*]}
#break ;;
*) (( INDEX=$1-1 ))
cd ${BOOKMARKS[${INDEX}]}
break ;;
esac
fi
}
Arrays in the Korn shell (and Bash and others) are sparse, so if you use unset to delete members of the array, you won't be able to use the size of the array as an index to the last member and other limitations.
Here are some useful snippets (the second for loop is something you might be able to put to use right away):
array=(1 2 3)
unset array[2]
echo ${array[2]} # null
indices=(${!array[#]}) # create an array of the indices of "array"
size=${#indices[#]} # the size of "array" is the number of indices into it
size=${#array[#]} # same
echo ${array[#]: -1} # you can use slices to get array elements, -1 is the last one, etc.
for element in ${array[#]}; do # iterate over the array without an index
for index in ${indices[#]} # iterate over the array WITH an index
do
echo "Index: ${index}, Element: ${array[index]}"
done
for index in ${!array[#]} # iterate over the array WITH an index, directly
That last one can eliminate the need for a counter.
Here are a couple more handy techniques:
array+=("new element") # append a new element without referring to an index
((counter++)) # shorter than ((counter=counter+1)) or ((counter+=1))
if [[ $var == 3 ]] # you can use the more "natural" comparison operators inside double square brackets
while [[ $var < 11 ]] # another example
echo ${array[${index}-1] # math inside an array subscript
This all assumes ksh93, some things may not work in earlier versions.
you can use unset. eg to delete array element 1
unset array[0]
to delete entire array
unset array
A few caveats regarding the previous answer:
First: I see this error all the time. When you provide an array element to "unset", you have to quote it. Consider:
$ echo foo > ./a2
$ ls a[2]
a2
$ a2="Do not delete this"
$ a=(this is not an array)
$ unset -v a[2]
$ echo "a2=${a2-UNSET}, a[]=${a[#]}"
a2=UNSET a[]=this is not an array
What happened? Globbing. You obviously wanted to delete element 2 of a[], but shell syntax being what it is, the shell first checked the current directory for a file that matched the glob pattern "a[2]". If it finds a match, it replaces the glob pattern with that filename, and you wind up making a decision about which variable to delete based on what files exist in your current directory.
This is profoundly stupid. But it's not something anyone has bothered to fix, apparently, and the error turns up in all kinds of documentation and example code from the last 3 decades.
Next is a related problem: it's easy to insert elements in your associative array with any key you like. But it's harder to remove these elements:
typeset -A assoc
key="foo] bar"
assoc[$key]=3 #No problem!
unset -v "assoc[$key]" #Problem!
In bash you can do this:
unset -v "assoc[\$key]"
In Korn Shell, you have to do this:
unset -v "assoc[foo\]\ bar]"
So it gets a bit more complicated in the case where your keys contain syntax characters.

Resources