NOTE: a permanent copy of this text is available on https://bpaste.net/raw/20a08beae676
this is intended as a self-answer tutorial on how to correctly remove a element from an array in a for loop (in this case i will assume u use a for i in array style for loop)
NOTE: this is intended for when u want to add or remove an element from a array that the for loop is using, for example, "for i in ${!apples[@]}" ; do blah ; done" if i blindly modify apples the variable i will not be modified accordinly and if u try to change i it will not work as this style of for loop does not permit doing so, this is a solution for that problem
for this we will use an edited version of https://stackoverflow.com/a/17533525/8680581
important
the first thing u will want to do is change the for loop into a C style equivilant that will enable you to do the job:
for (( i=0; i<${#li[*]}; i++ ));
this is equivilant to:
for i in ${!li[*]};
function:
remove_array() {
if [[ $1 == "-v" ]]
then
verbose=1
shift 1
fi
wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=$1
shift 1
wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes=($@)
if [[ ! -z $verbose ]]
then
echo "old array is $(eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array")"
fi
for i in ${wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes[@]}
do
if [[ ! -z $verbose ]]
then
echo "unsetting index $i"
fi
eval "unset $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[i]"
done
eval "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=(\"\${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}\")"
if [[ ! -z $verbose ]]
then
echo "new array is $(eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array")"
fi
if [[ ! -z $verbose ]]
then
unset verbose
fi
unset wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes
}
for those who think this function is overcomplicated, this is what it looks like with the variables non random and the verbose stuff cut out of it:
function (non randomized and no verbose option):
remove_array() {
a_array=$1
shift 1
a_indexes=($@)
for i in ${a_indexes[@]}
do
eval "unset $a_array[i]"
done
eval "$a_array=(\"\${$a_array[@]}\")"
unset a_array a_indexes
}
code:
li=(pi go dl og wa)
for (( i=0; i<${#li[*]}; i++ ));
do
echo "i before: $i"
echo "is array \"li\" index \"$i\" contents \"${li[i]}\" equal to \"go\" or \"og\"?"
if [[ "${li[i]}" == "go" || "${li[i]}" == "og" ]]
then
remove_array -v li $i
i=$(($i-1))
echo "setting i to $i here should cause i to repeat $i and check for \"go\" or \"og\" again"
fi
echo "i after: $i"
done
output:
i before: 0
is array "li" index "0" contents "pi" equal to "go" or "og"?
i after: 0
i before: 1
is array "li" index "1" contents "go" equal to "go" or "og"?
old array is declare -a li=([0]="pi" [1]="go" [2]="dl" [3]="og" [4]="wa")
unsetting index 1
new array is declare -a li=([0]="pi" [1]="dl" [2]="og" [3]="wa")
setting i to 0 here should cause i to repeat 0 and check for "go" or "og" again
i after: 0
i before: 1
is array "li" index "1" contents "dl" equal to "go" or "og"?
i after: 1
i before: 2
is array "li" index "2" contents "og" equal to "go" or "og"?
old array is declare -a li=([0]="pi" [1]="dl" [2]="og" [3]="wa")
unsetting index 2
new array is declare -a li=([0]="pi" [1]="dl" [2]="wa")
setting i to 1 here should cause i to repeat 1 and check for "go" or "og" again
i after: 1
i before: 2
is array "li" index "2" contents "wa" equal to "go" or "og"?
i after: 2
in depth overview
the code
first we initiate the loop
for (( i=0; i<${#li[*]}; i++ ));
do
then we set a condition upon the sub code will activate
if [[ "${li[i]}" == "go" || "${li[i]}" == "og" ]]
this will activate if the contents of array "li" index $i is equal to "go" or "og"
next we do something, in this case we remove "go" or "og" from the array "li" as so it does not get endlessly triggered when i is decreased
then
remove_array -v li $i
remove_array -v li $i will remove the index (or indexes if multiple are specified) $i (as in the value of the variable i, not "$i" literally) from the array "li" then it will shrink the array accordingly instead of leaving the index empty which is often undesired
then once that is done we decrease $i in order to account for the decrease in size of the array otherwise it will just skip array elements as if the array had not decreazed at all (which is bad)
i=$(($i-1))
this sets i to the value of i minus 1, for example
i = 3
expanded and variable substitution taken into account it will look like this
i=$((3-1))
>
i=$(3 minus 1) # note "minus" is not a real command nor valid syntax
>
3 - 1 = 2
>
i=2
after that we end the loop
fi
done
when i is decreased it will take effect in the for loop "i++"
2++
>
3
>
i-1
>
i=2
>
2++
the function
first we set up a small verbose flag for verbose output if we need it, and we check if positional argument 1 is equal to
-v
of the function (note positional arguments are as below: <> denotes non literal hints but intended as to hint what it CAN be according to what is accaptable)
(<function or command> arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 and so on for as many characters as ur terminal will allow in a single line)
if [[ $1 == "-v" ]]
then
if true we will set a variable
verbose=1
then we shift the positional arguments by 1, making arg1 become arg0, arg2 become arg1, arg3 become arg2 and so on
shift 1
and finish the setup
fi
then we semi-randomize the "array" variable and "index" array, then we set _array ( denotes the long semi-random string) to be positional argument 1 of the function
wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=$1
note since it is unlikely that an array name will contain characters that needs quoting we will not quote positional argument 1
then we shift the positional arguments by 1
shift 1
then we set *_index to all of the positional arguments to allow for specifying of multiple indexes, and we make it an array
wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes=($@)
then we test if $verbose exists
if [[ ! -z $verbose ]]
and if so, print something
then
echo "old array is $(eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array")"
fi
notice how we use eval, eval can be used to execute commands that would otherwise be impossible to execute due to the command itself being literally dependant on a variable of some sort, note eval works (from my understanding) by first preforming a ecko/printf then executing the result (much like
echo "ls /" | bash -
only the it is not executed in a subshell and thus has the advantage of veing able to work with the script and has alot of possibilities for its use case, for example u might use it to dynamically generate an if statement that is dependant on an array as to how many if/elif statements there will be which saves alot of time especially if it needs to be done for lots of functions and the array contents is unknown (as you would literally need to change every if statement every time u want to add or remove a needed/uneeded variable, such as a title in a list of movies u watched, or a list of websites to check in a complex order that can easily end up being thousands of lines of code total just to check them all in every function, reduced to just a few lines of code template with eval)
eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array"
in this eval, it evaluates the following, assuming *_array contains "array_mine"
eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array"
>
eval "declare -p array_mine"
>
declare -p array_mine
then it executes declare -p array_mine
next we loop over the array of indexes and unset each index
for i in ${wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes[@]}
do
if [[ ! -z $verbose ]]
then
echo "unsetting index $i"
fi
eval "unset $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[i]"
done
in this eval it is different but still the same concept, again we will assume *_array is array_mine
eval "unset $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[i]"
>
eval "unset array_mine[i]"
>
unset array_mine[i]
note that since "i" itself was not mentioned as a variabe explicitely eval does not evaluate it, but it would if it is eval "unset array_mine[$i]" however is useless since the for loop evaluates i instead as it is a paramater of the index value and is directly a number
next finish the loop
done
then we shrink the array, this is relatively easy to do
eval "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=(\"\${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}\")"
substitution in place:
eval "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=(\"\${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}\")"
>
eval "array_mine=(\"\${array_mine[@]}\")" # notice how we backslash the quotes and the array, this is done to prevent them from being evaluated or unquoting the quote thus making anything after the quote be interperated as literal command
>
array_mine=("${array_mine[@]}")
and in case you are wondering here is what would happen if the \ had not have been added
$ <eval or echo or printf> "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=("${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}")"
bash: "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=("${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}")": bad substitution
$ <eval or echo or printf> "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=(\"${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}\")"
bash: $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=("${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}"): bad substitution
$ <eval or echo or printf> "$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array=("\${$wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array[@]}")"
li=(${li[@]})
notice how on the last one it prints it with no quoting but seems to print it even though "..."..."..." should be unquoting then requoting
then to finish up we print the last of out verbose output then unset our variables and end the function, by now im sure you all know how eval works (at least for simple stuff, as eval can be used in very complex ways)
if [[ ! -z $verbose ]]
then
echo "new array is $(eval "declare -p $wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array")"
fi
if [[ ! -z $verbose ]]
then
unset verbose
fi
unset wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_array wbgjsgvyueswbgbaeswgrvbseurbhguyewrhbgvuesw4rgyuvaifewbyguvaewrusgbfvauq3wegbvyuaevr_indexes
}
sample output of the function remove_array (of both verbose and non verbose):
$ array=(1 2 3 4 5 6 8 9 10 7)
$ remove_array -v array 5 2
old array is
declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="8" [7]="9" [8]="10" [9]="7")
unsetting index 5
unsetting index 2
new array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="8" [5]="9" [6]="10" [7]="7")
$ declare -p array
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="8" [5]="9" [6]="10" [7]="7")
$ array=(1 2 3 4 5 6 8 9 10 7)
$ remove_array array 1 8 3
$ declare -p array
declare -a array=([0]="1" [1]="3" [2]="5" [3]="6" [4]="8" [5]="9" [6]="7")
$
note this CAN be used recursively (such as removing every second index, eg
$ array=(1 2 3 4 5 6 8 9 10 7)
$ remove_array -v array 2
old array is
declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="8" [7]="9" [8]="10" [9]="7")
unsetting index 2
new array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="6" [5]="8" [6]="9" [7]="10" [8]="7")
$ remove_array -v array 4
old array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="6" [5]="8" [6]="9" [7]="10" [8]="7")
unsetting index 4
new array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="8" [5]="9" [6]="10" [7]="7")
$ remove_array -v array 6
old array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="8" [5]="9" [6]="10" [7]="7")
unsetting index 6
new array is
declare -a array=([0]="1" [1]="2" [2]="4" [3]="5" [4]="8" [5]="9" [6]="7")
$
and so on)
(although it is dangous to do so as wanted array indexes may be removed)
also works on arrays with spaces
$ array=("1 2" "3 4" "5 6" "8 9" "10 7" 8 9 7 3)
$ remove_array -v array 3 7
old array is
declare -a array=([0]="1 2" [1]="3 4" [2]="5 6" [3]="8 9" [4]="10 7" [5]="8" [6]="9" [7]="7" [8]="3")
unsetting index 3
unsetting index 7
new array is
declare -a array=([0]="1 2" [1]="3 4" [2]="5 6" [3]="10 7" [4]="8" [5]="9" [6]="3")
$
as a side note here is two eval statement that evaluates some printed code
eval "$(echo ls /)"
test_func() {
directories=( \
bin \
lib \
usr \
proc \
)
printf "dirs=(
/tmp
/dev
"
for i in ${!directories[@]}
do
printf '%b' "
/${directories[i]}
"
done
printf ")
"
printf 'dirs_filtered() {
for i in ${!dirs[@]}
do
if [[ ${dirs[i]} =~ "/tm" ]]
then
printf "${dirs[i]} detected
"
elif [[ ${dirs[i]} =~ "/proc" ]]
then
printf "${dirs[i]} detected and is a special file system
"
fi
done
}
dirs_filtered
'
}
eval "$(test_func)"
Output: