161 votes

Retirer un élément d'un tableau Bash

J'ai besoin de supprimer un élément d'un tableau dans le shell bash. Généralement, je ferais simplement :

array=("${(@)array:#<element to remove>}")

Malheureusement, l'élément que je veux supprimer est une variable et je ne peux donc pas utiliser la commande précédente. Voici un exemple :

array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}

Une idée ?

241voto

chepner Points 54078

Ce qui suit fonctionne comme vous le souhaitez dans bash y zsh :

$ array=(pluto pippo)
$ delete=pluto
$ echo ${array[@]/$delete}
pippo
$ array=( "${array[@]/$delete}" ) #Quotes when working with strings

Si vous avez besoin de supprimer plus d'un élément :

...
$ delete=(pluto pippo)
for del in ${delete[@]}
do
   array=("${array[@]/$del}") #Quotes when working with strings
done

Caveat

Cette technique supprime en fait les préfixes correspondant $delete des éléments, pas nécessairement des éléments entiers.

Mise à jour

Pour supprimer réellement un élément précis, vous devez parcourir le tableau, en comparant la cible à chaque élément, et en utilisant la fonction unset pour supprimer une correspondance exacte.

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

Notez que si vous faites cela, et qu'un ou plusieurs éléments sont supprimés, les indices ne seront plus une séquence continue d'entiers.

$ declare -p array
declare -a array=([0]="pluto" [2]="bob")

Le fait est que les tableaux n'ont pas été conçus pour être utilisés comme structures de données mutables. Ils sont principalement utilisés pour stocker des listes d'éléments dans une seule variable sans avoir besoin de gaspiller un caractère comme délimiteur (par exemple, pour stocker une liste de chaînes de caractères qui peuvent contenir des espaces).

Si les vides sont un problème, vous devez alors reconstruire le tableau pour combler les vides :

for i in "${!array[@]}"; do
    new_array+=( "${array[i]}" )
done
array=("${new_array[@]}")
unset new_array

35voto

Steve Kehlet Points 1294

Vous pourriez créer un nouveau tableau sans l'élément indésirable, puis le réaffecter à l'ancien tableau. Cela fonctionne dans bash :

array=(pluto pippo)
new_array=()
for value in "${array[@]}"
do
    [[ $value != pluto ]] && new_array+=($value)
done
array=("${new_array[@]}")
unset new_array

Cela donne :

echo "${array[@]}"
pippo

19voto

signull Points 299

C'est le moyen le plus direct pour annuler une valeur si vous connaissez sa position.

$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2

9voto

dash-o Points 7093

Cette réponse est spécifique au cas de la suppression de plusieurs valeurs dans de grands tableaux, où les performances sont importantes.

Les solutions les plus votées sont (1) la substitution de motifs sur un tableau, ou (2) l'itération sur les éléments du tableau. La première est rapide, mais ne peut traiter que les éléments qui ont un préfixe distinct, la seconde a O(n*k), n=taille du tableau, k=éléments à supprimer. Les tableaux associatifs sont une fonctionnalité relativement nouvelle, qui n'était peut-être pas courante lorsque la question a été posée à l'origine.

Pour le cas de la correspondance exacte, avec de grands n et k, il est possible d'améliorer les performances de O(n k) à O(n+k log(k)). En pratique, O(n) suppose que k est beaucoup plus petit que n. La plupart de l'accélération est basée sur l'utilisation de tableaux associatifs pour identifier les éléments à supprimer.

Performance (taille du tableau n, k-valeurs à supprimer). Mesure de la performance secondes de temps d'utilisation

   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%

Comme prévu, le current est linéaire par rapport à N*K, et la solution de fast est pratiquement linéaire par rapport à K, avec une constante beaucoup plus faible. Le site fast est légèrement plus lente que la solution current solution lorsque k=1, en raison de la configuration supplémentaire.

La solution "rapide" : array=liste des entrées, delete=liste des valeurs à supprimer.

        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[@]}")

Comparaison avec current solution, à partir de la réponse la plus votée.

    for target in "${delete[@]}"; do
        for i in "${!array[@]}"; do
            if [[ ${array[i]} = $target ]]; then
                unset 'array[i]'
            fi
        done
    done
    array=("${array[@]}")

6voto

Niklas Holm Points 256

Voici une solution en une ligne avec mapfile :

$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[@]}" | grep -Pzv "<regexp>")

Exemple :

$ 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

Cette méthode permet une grande flexibilité en modifiant la commande grep et ne laisse aucune chaîne vide dans le tableau.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X