Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
587 views
in Technique[技术] by (71.8m points)

linux - how to remove an array element correctly in a for loop

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
# define an array
foo=( a b c d e f )

# delete element 2 ("c") from array
unset foo[2]

# copy array
foo=("${foo[@]}")

# show array
declare -p foo

Output:

declare -a foo='([0]="a" [1]="b" [2]="d" [3]="e" [4]="f")'

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...