web-dev-qa-db-de.com

Entfernen Sie ein Element aus einem Bash-Array

Ich muss ein Element aus einem Array in der bash-Shell entfernen .. __ Im Allgemeinen würde ich einfach Folgendes tun:

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

Leider ist das Element, das ich entfernen möchte, eine Variable, daher kann ich den vorherigen Befehl nicht verwenden. Hier ein Beispiel:

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

Irgendeine Idee?

67
Alex

Das Folgende funktioniert in bash und zsh wie Sie möchten:

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

Wenn Sie mehr als ein Element löschen müssen:

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

Vorbehalt

Diese Technik entfernt tatsächlich Präfixe, die mit $delete übereinstimmen, aus den Elementen, nicht unbedingt aus ganzen Elementen.

Update

Um ein genaues Element wirklich zu entfernen, müssen Sie durch das Array gehen, das Ziel mit jedem Element vergleichen und unset verwenden, um eine genaue Übereinstimmung zu löschen.

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

Wenn Sie dies tun und ein oder mehrere Elemente entfernt werden, sind die Indizes nicht länger eine fortlaufende Folge von Ganzzahlen.

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

Die einfache Tatsache ist, dass Arrays nicht für die Verwendung als veränderliche Datenstrukturen konzipiert wurden. Sie werden hauptsächlich zum Speichern von Listen von Elementen in einer einzelnen Variablen verwendet, ohne dass ein Zeichen als Trennzeichen verschwendet werden muss (z. B. zum Speichern einer Liste von Zeichenfolgen, die Leerzeichen enthalten können).

Wenn Lücken ein Problem sind, müssen Sie das Array neu erstellen, um die Lücken zu füllen:

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

Sie können ein neues Array ohne das unerwünschte Element aufbauen und es dann wieder dem alten Array zuweisen. Dies funktioniert in bash:

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

Dies ergibt:

echo "${array[@]}"
pippo
18
Steve Kehlet

Dies ist der direkteste Weg, um einen Wert aufzuheben, wenn Sie wissen, wo er sich befindet.

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

Um die obigen Antworten zu erweitern, können Sie folgende Elemente verwenden, um mehrere Elemente ohne teilweise Übereinstimmung aus einem Array zu entfernen:

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

Dies führt zu einem Array, das Folgendes enthält: (Zwei auf drei, drei, "eins, sechs")

2
Dylan

Hier ist eine einzeilige Lösung mit Mapfile:

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

Beispiel:

$ 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

Diese Methode bietet große Flexibilität durch Ändern/Austauschen des Befehls grep und lässt keine leeren Zeichenfolgen im Array zurück.

2
Niklas Holm

Das POSIX-Shell-Skript hat keine Arrays.

Wahrscheinlich verwenden Sie also einen bestimmten Dialekt wie bash, Kornmuscheln oder zsh.

Daher kann Ihre Frage ab sofort nicht beantwortet werden.

Vielleicht funktioniert das für Sie:

unset array[$delete]
1
Anony-Mousse

Hier ist eine (wahrscheinlich sehr bash-spezifische) kleine Funktion, die die Indirektion der bash-Variablen und unset beinhaltet. Hierbei handelt es sich um eine allgemeine Lösung, bei der keine Textelemente ersetzt oder leere Elemente verworfen werden und es keine Probleme mit Zitaten/Leerzeichen usw. gibt.

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

Verwenden Sie es wie delete_ary_elmt ELEMENT ARRAYNAME ohne $-Siegel. Wechseln Sie den == $Word in == $Word* für Präfix-Übereinstimmungen. Verwenden Sie ${elmt,,} == ${Word,,} für Übereinstimmungen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. usw., was auch immer bash [[ unterstützt.

Es funktioniert, indem die Indizes des Eingabearrays bestimmt und rückwärts iteriert werden (durch das Löschen von Elementen wird die Reihenfolge der Iterationen nicht beeinträchtigt). Um die Indizes zu erhalten, müssen Sie mit dem Namen auf das Eingabe-Array zugreifen. Dies kann über die Indirektion der Bash-Variablen x=1; varname=x; echo ${!varname} # prints "1" erfolgen.

Sie können auf Arrays nicht über den Namen wie aryname=a; echo "${$aryname[@]} zugreifen. Dies gibt einen Fehler. Sie können aryname=a; echo "${!aryname[@]}" nicht tun, dies gibt Ihnen die Indizes der Variablen aryname (obwohl es kein Array ist). Was funktioniert, ist aryref="a[@]"; echo "${!aryref}", wodurch die Elemente des Arrays a gedruckt werden. Shell-Word-Anführungszeichen und Whitespace werden genauso wie echo "${a[@]}" beibehalten. Dies funktioniert jedoch nur zum Drucken der Elemente eines Arrays, nicht zum Drucken der Länge oder der Indizes (aryref="!a[@]" oder aryref="#a[@]" oder "${!!aryref}" oder "${#!aryref}", alle schlagen fehl).

Also kopiere ich das ursprüngliche Array per Bash Indirection bei seinem Namen und bekomme die Indizes aus der Kopie. Um die Indizes in umgekehrter Reihenfolge zu durchlaufen, verwende ich einen C-Style für die Schleife. Ich könnte es auch tun, indem ich über ${!arycopy[@]} auf die Indizes zugreife und sie mit tac umkehre, was eine cat ist, die die Reihenfolge der Eingabezeile umkehrt.

Eine Funktionslösung ohne variable Indirektion würde wahrscheinlich eval beinhalten müssen, was in dieser Situation möglicherweise nicht sicher ist (ich kann es nicht sagen).

1
S.V.P.

Informationen zum Vermeiden von Konflikten mit dem Arrayindex mithilfe von unset finden Sie unter https://stackoverflow.com/a/49626928/3223785 und https://stackoverflow.com/a/47798640)/3223785 Für weitere Informationen - ordnen Sie das Array sich selbst zu: 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/ ]

0
Eduardo Lucio

unset verwenden

Um ein Element an einem bestimmten Index zu entfernen, können wir unset verwenden und dann in ein anderes Array kopieren. Nur gerade unset ist in diesem Fall nicht erforderlich. Da unset das Element nicht entfernt, setzt es lediglich eine leere Zeichenfolge auf den jeweiligen Index im 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]}"

Ausgabe ist

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

Verwenden von :<idx>

Wir können einige Elemente auch mit :<idx> entfernen. Wenn wir zum Beispiel das erste Element entfernen möchten, können wir :1 wie unten erwähnt verwenden.

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

Ausgabe ist 

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

In ZSH ist dies ein Kinderspiel (beachten Sie, dass dies zur Vereinfachung des Verständnisses mehr Bash-kompatible Syntax als erforderlich verwendet, als möglich):

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

Ergebnisse:

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

Nur teilweise Antwort

So löschen Sie das erste Element im Array

unset 'array[0]'

Um das letzte Element im Array zu löschen

unset 'array[-1]'
0
consideRatio

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

$ {PARAMETER # PATTERN} # vom Anfang entfernen

$ {PARAMETER ## PATTERN} # Vom Anfang entfernen, gierige Übereinstimmung

$ {PARAMETER% PATTERN} # vom Ende entfernen

$ {PARAMETER %% PATTERN} # vom Ende entfernen, gierige Übereinstimmung

Um ein vollständiges Element zum Entfernen auszuführen, müssen Sie einen Befehl zum Zurücksetzen mit einer if-Anweisung ausführen. Wenn Sie keine Präfixe aus anderen Variablen entfernen oder Whitespace im Array unterstützen möchten, können Sie die Anführungszeichen weglassen und die Schleifen vergessen. 

Im folgenden Beispiel finden Sie einige Möglichkeiten zum Bereinigen eines Arrays.

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

Ausgabe

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

Hoffentlich hilft das.

0
phyatt

Eigentlich ist mir gerade aufgefallen, dass die Shell-Syntax etwas eingebautes Verhalten aufweist, das eine einfache Rekonstruktion des Arrays ermöglicht, wenn ein Element, wie in der Frage gestellt, entfernt werden sollte.

# 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

Beachten Sie, wie wir das Array mit der x+=()-Syntax von bash erstellt haben?

Sie könnten tatsächlich mehr als ein Element hinzufügen, den Inhalt eines ganzen anderen Arrays auf einmal.

0
mar77i

Es gibt auch diese Syntax, z. wenn Sie das 2. Element löschen wollen:

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

dies ist in der Tat die Verkettung von 2 Registerkarten. Der Erste vom Index 0 bis zum Index 1 (exklusiv) und der Zweite vom Index 2 bis zum Ende.

0
OphyTe