Usuwanie elementu z tablicy Bash

Muszę usunąć element z tablicy w powłoce bash. Generalnie po prostu bym zrobił:

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

Niestety element, który chcę usunąć jest zmienną, więc nie mogę użyć poprzedniego polecenia. Poniżej przykład:

array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}
Jakiś pomysł?
Author: codeforester, 2013-05-31

16 answers

Następujące prace jak chcesz w bash i zsh:

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

Jeśli trzeba usunąć więcej niż jeden element:

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

Zastrzeżenie

Ta technika faktycznie usuwa przedrostki pasujące $delete z elementów, niekoniecznie całych elementów.

Update

Aby naprawdę usunąć dokładny element, musisz przejść przez tablicę, porównując obiekt docelowy z każdym elementem i używając unset do usunięcia dokładnego dopasowania.

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

Uwaga jeśli to zrobisz, a jeden lub więcej elementów zostanie usuniętych, indeksy nie będą już ciągiem liczb całkowitych.

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

Faktem jest, że tablice nie zostały zaprojektowane do wykorzystania jako zmienne struktury danych. Są one używane przede wszystkim do przechowywania list elementów w pojedynczej zmiennej bez konieczności marnowania znaków jako ogranicznika(np. do przechowywania listy łańcuchów, które mogą zawierać białe znaki).

Jeśli luki są problemem, musisz odbudować tablicę, aby wypełnić luki:

for i in "${!array[@]}"; do
    new_array+=( "${array[i]}" )
done
array=("${new_array[@]}")
unset new_array
 98
Author: chepner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-13 10:39:42

Można zbudować nową tablicę bez niepożądanego elementu, a następnie przypisać ją z powrotem do starej tablicy. To działa w bash:

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

Daje to:

echo "${array[@]}"
pippo
 16
Author: Steve Kehlet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-29 18:21:41

Aby rozwinąć powyższe odpowiedzi, można użyć następujących elementów do usunięcia wielu elementów z tablicy, bez częściowego dopasowania:

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

Spowoduje to powstanie tablicy zawierającej: (two onetwo three threefour "one six")

 2
Author: Dylan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-16 09:48:06

Jest to najbardziej bezpośredni sposób na wyłączenie wartości, jeśli znasz jej pozycję.

$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2
 2
Author: signull,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-13 17:05:58

Skrypt powłoki POSIX nie posiada tablic.

Więc najprawdopodobniej używasz określonego dialektu, takiego jak bash, Korn czy zsh.

W związku z tym, na twoje pytanie od teraz nie można odpowiedzieć.

Może to działa dla Ciebie:

unset array[$delete]
 1
Author: Anony-Mousse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-31 15:38:45

Oto (prawdopodobnie bardzo specyficzna dla Basha) mała funkcja z indirection zmiennej bash i unset; jest to ogólne rozwiązanie, które nie wymaga zastępowania tekstu lub odrzucania pustych elementów i nie ma problemów z cytowaniem / białymi spacjami itp.

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

Użyj go jak delete_ary_elmt ELEMENT ARRAYNAME bez sigila $. Przełącz == $word dla == $word* dla dopasowań prefiksów; użyj {[6] } dla dopasowań bez rozróżniania wielkości liter; itd., cokolwiek wspiera bash [[.

Działa poprzez wyznaczenie wskaźników input array i iteracja nad nimi do tyłu (więc usuwanie elementów nie psuje kolejności iteracji). Aby uzyskać indeksy, musisz uzyskać dostęp do tablicy wejściowej według nazwy, co można zrobić za pomocą zmiennej Bash indirection x=1; varname=x; echo ${!varname} # prints "1".

Nie możesz uzyskać dostępu do tablic po nazwie, jak aryname=a; echo "${$aryname[@]}, to oznacza błąd. Nie możesz zrobić aryname=a; echo "${!aryname[@]}", to daje Ci indeksy zmiennej aryname (chociaż nie jest to tablica). Działa aryref="a[@]"; echo "${!aryref}", który wyświetli elementy tablicy a, zachowując powłoka-cytowanie słów i spacji dokładnie tak jak echo "${a[@]}". Ale to działa tylko do drukowania elementów tablicy, a nie do drukowania jej długości lub indeksów (aryref="!a[@]" lub aryref="#a[@]" lub "${!!aryref}" lub "${#!aryref}", wszystkie one zawodzą).

Więc kopiuję oryginalną tablicę według jej nazwy poprzez Bash indirection i pobieram indeksy z kopii. Do iteracji indeksów w odwrotnej kolejności używam pętli w stylu C. Mogę to zrobić również poprzez dostęp do indeksów za pomocą ${!arycopy[@]} i odwrócenie ich za pomocą tac, czyli cat, który odwraca wokół kolejności linii wejściowych.

Rozwiązanie funkcji bez zmiennej indrection prawdopodobnie musiałoby obejmować eval, które mogą, ale nie muszą być bezpieczne w takiej sytuacji(nie mogę powiedzieć).

 1
Author: S.V.P.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-15 18:17:54

Oto jednoliniowe rozwiązanie z mapfile:

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

Przykład:

$ 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

Ta metoda pozwala na dużą elastyczność poprzez modyfikację / wymianę polecenia grep i nie pozostawia żadnych pustych łańcuchów w tablicy.

 1
Author: Niklas Holm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-23 08:26:14

Właściwie, zauważyłem, że składnia powłoki ma wbudowane zachowanie, które pozwala na łatwą rekonstrukcję tablicy, gdy, jak postawiono w pytaniu, element powinien zostać usunięty.

# 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

Zauważ jak skonstruowaliśmy tablicę używając składni x+=() Basha?

Możesz dodać więcej niż jeden element z tym, zawartość całej innej tablicy naraz.

 0
Author: mar77i,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-21 07:25:39

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

${parametr # wzorzec} # usuń z początku

${parametr# # wzorzec} # usuń z początku, dopasuj

${parametr % wzorzec} # usuń z końca

${parametr % % wzorzec} # remove from the end, greedy match

Aby wykonać pełny remove element, musisz wykonać polecenie unset z instrukcją if. Jeśli nie zależy ci na usunięciu prefiksy z innych zmiennych lub o obsługę białych znaków w tablicy, wtedy możesz po prostu porzucić cudzysłowy i zapomnieć o pętlach for.

Zobacz przykład poniżej dla kilku różnych sposobów czyszczenia tablicy.

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

Wyjście

Choose an option:
1) foobar
2) foo bar
3) s
4) Quit
Option? 
Mam nadzieję, że to pomoże.
 0
Author: phyatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-15 19:05:32

Za pomocą unset

Aby usunąć element z określonego indeksu, możemy użyć unset, a następnie skopiować do innej tablicy. Tylko unset nie jest wymagane w tym przypadku. Ponieważ unset nie usuwa elementu, ustawia null string do określonego indeksu w tablicy.

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

Wyjście to

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

Za pomocą :<idx>

Możemy również usunąć niektóre elementy używając :<idx>. Na przykład jeśli chcemy usunąć 1. element możemy użyć :1 jako wymienione poniżej.

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

Wyjście to

bb cc dd ee
1st val is cc, 2nd val is dd
 0
Author: rashok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-03 09:43:17

W ZSH jest to bardzo proste (zauważ, że używa składni zgodnej z bash, która jest bardziej niż to konieczne, jeśli jest to możliwe dla ułatwienia zrozumienia):

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

Wyniki:

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
Author: trevorj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-11 08:22:03

Tylko częściowa odpowiedź

Aby usunąć pierwszą pozycję w tablicy

unset array[0]

Aby usunąć ostatnią pozycję w tablicy

unset array[-1]
 0
Author: consideRatio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-19 09:17:00

To co robię to:

array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"

BAM, ten element jest usuwany.

 -1
Author: garfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-24 17:34:13

Jest to szybkie i brudne rozwiązanie, które będzie działać w prostych przypadkach, ale pęknie, jeśli (a) w $delete są znaki specjalne regex lub (b) w dowolnych elementach są spacje. Zaczynając od:

array+=(pluto)
array+=(pippo)
delete=(pluto)

Usuń wszystkie wpisy dokładnie pasujące $delete:

array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)

W wyniku echo $array - > pippo, i upewniając się, że to tablica: echo $array[1] - > pippo

fmt jest trochę niejasne: fmt -1 owija się w pierwszej kolumnie (aby umieścić każdy element w swojej własnej linii. I tu problem powstaje z przedmiotów w przestrzeniach.) fmt -999999 rozpakowuje go z powrotem do jednej linii, przywracając odstępy między przedmiotami. Są na to inne sposoby, np. xargs.

Dodatek: jeśli chcesz usunąć tylko pierwsze dopasowanie, Użyj sed, zgodnie z opisem tutaj :

array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
 -1
Author: Joshua Goldberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-01 14:44:00

A może coś w stylu:

array=(one two three)
array_t=" ${array[@]} "
delete=one
array=(${array_t// $delete / })
unset array_t
 -1
Author: user8223227,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-28 00:11:50
#/bin/bash

echo "# define array with six elements"
arr=(zero one two three 'four 4' five)

echo "# unset by index: 0"
unset -v 'arr[0]'
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

arr_delete_by_content() { # value to delete
        for i in ${!arr[*]}; do
                [ "${arr[$i]}" = "$1" ] && unset -v 'arr[$i]'
        done
        }

echo "# unset in global variable where value: three"
arr_delete_by_content three
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

echo "# rearrange indices"
arr=( "${arr[@]}" )
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

delete_value() { # value arrayelements..., returns array decl.
        local e val=$1; new=(); shift
        for e in "${@}"; do [ "$val" != "$e" ] && new+=("$e"); done
        declare -p new|sed 's,^[^=]*=,,'
        }

echo "# new array without value: two"
declare -a arr="$(delete_value two "${arr[@]}")"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

delete_values() { # arraydecl values..., returns array decl. (keeps indices)
        declare -a arr="$1"; local i v; shift
        for v in "${@}"; do 
                for i in ${!arr[*]}; do
                        [ "$v" = "${arr[$i]}" ] && unset -v 'arr[$i]'
                done
        done
        declare -p arr|sed 's,^[^=]*=,,'
        }
echo "# new array without values: one five (keep indices)"
declare -a arr="$(delete_values "$(declare -p arr|sed 's,^[^=]*=,,')" one five)"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

# new array without multiple values and rearranged indices is left to the reader
 -2
Author: Gombok Arthur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-17 19:16:11