web-dev-qa-db-fra.com

Supprimer un élément d'un tableau de Bash

Je dois supprimer un élément d'un tableau dans bash Shell . En général, je ferais simplement:

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

Malheureusement, l'élément que je veux supprimer est une variable, 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?

67
Alex

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

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

Si 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 les préfixes correspondant à $delete des éléments, pas nécessairement des éléments entiers.

Mettre à jour

Pour vraiment supprimer un élément exact, vous devez parcourir le tableau, en comparant la cible à chaque élément et en utilisant 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]} = "${delete[0]}" ]]; then
      unset 'array[i]'
    fi
  done
done

Notez que si vous procédez ainsi et qu'un ou plusieurs éléments sont supprimés, les index 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 à gaspiller un caractère en tant que séparateur (par exemple, pour stocker une liste de chaînes pouvant contenir des espaces).

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

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

Vous pouvez créer un nouveau tableau sans l'élément indésirable, puis le réassigner à 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
18
Steve Kehlet

C'est le moyen le plus direct de supprimer une valeur si vous connaissez sa position.

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

Pour développer les réponses ci-dessus, vous pouvez utiliser les méthodes suivantes pour supprimer plusieurs éléments d'un tableau, sans correspondance partielle:

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

Cela donnera un tableau contenant: (Deux sur trois trois quatre "un six")

2
Dylan

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/échangeant la commande grep et ne laisse aucune chaîne vide dans le tableau.

2
Niklas Holm

Le script POSIX Shell n'a pas de tableaux.

Donc, vous utilisez probablement un dialecte spécifique tel que bash, korn shell ou zsh.

Par conséquent, vous ne pouvez pas répondre à votre question dès maintenant.

Peut-être que cela fonctionne pour vous:

unset array[$delete]
1
Anony-Mousse

Voici une petite fonction (probablement très spécifique à bash) impliquant la variable bash indirection et unset; c'est une solution générale qui n'implique pas de substitution de texte ni d'élimination d'éléments vides et ne présente aucun problème pour citer/espaces blancs, 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

Utilisez-le comme delete_ary_elmt ELEMENT ARRAYNAME sans aucun sigil $. Changer le == $Word pour == $Word* pour les correspondances de préfixe; utilisez ${elmt,,} == ${Word,,} pour les correspondances sans distinction de casse; etc., tout ce que bash [[ supporte.

Cela fonctionne en déterminant les index du tableau en entrée et en les itérant à l'envers (ainsi, la suppression d'éléments ne bousille pas l'ordre des itérations). Pour obtenir les index, vous devez accéder au tableau d'entrée par nom, ce qui peut être fait via la variable bash indirection x=1; varname=x; echo ${!varname} # prints "1".

Vous ne pouvez pas accéder aux tableaux par leur nom comme aryname=a; echo "${$aryname[@]}, cela vous donnera une erreur. Vous ne pouvez pas faire aryname=a; echo "${!aryname[@]}", cela vous donne les indices de la variable aryname (bien que ce ne soit pas un tableau). Ce qui fonctionne est aryref="a[@]"; echo "${!aryref}", qui imprimera les éléments du tableau a, en préservant les guillemets Shell-Word et les espaces, exactement comme echo "${a[@]}". Mais cela ne fonctionne que pour imprimer les éléments d'un tableau, pas pour imprimer sa longueur ou ses index (aryref="!a[@]" ou aryref="#a[@]" ou "${!!aryref}" ou "${#!aryref}", ils échouent tous).

Je copie donc le tableau d'origine par son nom via bash indirection et récupère les index de la copie. Pour parcourir les index en sens inverse, j'utilise une boucle de style C pour. Je pourrais aussi le faire en accédant aux index via ${!arycopy[@]} et en les inversant avec tac, qui est une cat qui permet de changer l'ordre de la ligne d'entrée.

Une solution de fonction sans indirection variable devrait probablement impliquer eval, ce qui peut ou non être sûr à utiliser dans cette situation (je ne peux pas dire).

1
S.V.P.

Pour éviter les conflits avec l'index de tableau à l'aide de unset - voir https://stackoverflow.com/a/49626928/3223785 et https://stackoverflow.com/a/47798640/3223785 Pour plus d'informations, réaffectez le tableau à lui-même: 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

[Réf .: https://tecadmin.net/working-with-array-bash-script/ ]

0
Eduardo Lucio

Utilisation de unset

Pour supprimer un élément à un index particulier, nous pouvons utiliser unset et ensuite copier dans un autre tableau. Seul unset n'est pas requis dans ce cas. Étant donné que unset ne supprime pas l'élément, il ne fait que définir une chaîne null pour l'index particulier du tableau. 

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]}"

La sortie est

aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd

Utilisation de :<idx>

Nous pouvons également supprimer un ensemble d’éléments en utilisant :<idx>. Par exemple, si nous voulons supprimer le 1er élément, nous pouvons utiliser :1 comme mentionné ci-dessous.

declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[@]:1}")
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"

La sortie est 

bb cc dd ee
1st val is cc, 2nd val is dd
0
rashok

En ZSH, cela est extrêmement facile (notez que cela utilise plus de syntaxe compatible bash que nécessaire si possible pour faciliter la compréhension):

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

Résultats:

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
0
trevorj

Réponse partielle seulement

Pour supprimer le premier élément du tableau

unset 'array[0]'

Pour supprimer le dernier élément du tableau

unset 'array[-1]'
0
consideRatio

http://wiki.bash-hackers.org/syntax/pe#substring_removal

$ {PARAMETER # PATTERN} # supprimer du début

$ {PARAMETER ## PATTERN} # supprimer du début, match gourmand

$ {PARAMETER% PATTERN} # supprimer de la fin

$ {PARAMETER %% PATTERN} # supprimer de la fin, match gourmand

Pour faire un élément remove complet, vous devez faire une commande unset avec une instruction if. Si vous ne vous souciez pas de supprimer les préfixes d'autres variables ou de prendre en charge les espaces dans le tableau, vous pouvez simplement supprimer les guillemets et oublier les boucles for. 

Voir l'exemple ci-dessous pour connaître différentes façons de nettoyer un tableau.

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

Sortie

Choose an option:
1) foobar
2) foo bar
3) s
4) Quit
Option? 

J'espère que cela pourra aider.

0
phyatt

En fait, je viens de remarquer que la syntaxe du shell a en quelque sorte un comportement intégré qui permet une reconstruction facile du tableau lorsque, comme le pose la question, un élément doit être supprimé.

# 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

Remarquez comment nous avons construit le tableau en utilisant la syntaxe x+=() de bash?

Vous pouvez en fait ajouter plusieurs éléments à cela, le contenu d'un autre tableau à la fois.

0
mar77i

Il y a aussi cette syntaxe, par exemple si vous voulez supprimer le 2ème élément:

array=("${array[@]:0:1}" "${array[@]:2}")

ce qui est en fait la concaténation de 2 onglets. Le premier de l'index 0 à l'index 1 (exclusif) et le second de l'index 2 à la fin.

0
OphyTe