Удаление элемента из массива Bash
мне нужно удалить элемент из массива в оболочке bash.
Вообще я бы просто сделал:
array=("${(@)array:#<element to remove>}")
к сожалению, элемент, который я хочу удалить, является переменной, поэтому я не могу использовать предыдущую команду.
Вот пример:
array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}
есть идеи?
16 ответов:
следующие работы в
bashиzsh:$ array=(pluto pippo) $ delete=(pluto) $ echo ${array[@]/$delete} pippo $ array=( "${array[@]/$delete}" ) #Quotes when working with stringsесли нужно удалить более одного элемента:
... $ delete=(pluto pippo) for del in ${delete[@]} do array=("${array[@]/$del}") #Quotes when working with strings doneбудьте осторожны
этот метод фактически удаляет префиксы, соответствующие
$deleteиз элементов, не обязательно все элементы.обновление
чтобы действительно удалить точный элемент, вам нужно пройти через массив, сравнивая цель с каждым элементом, и используя
unsetудалить точное совпадение.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обратите внимание, что если вы сделаете это, и один или несколько элементов будут удалены, индексы больше не будут непрерывной последовательностью целых чисел.
$ declare -p array declare -a array=([0]="pluto" [2]="bob")простой факт, что массивы не предназначены для использования в качестве изменяемых структур данных. Они в основном используются для хранения списков элементов в одной переменной без необходимости тратить символ в качестве разделителя (например, для хранения списка строк, которые могут содержать пробел.)
если пробелы являются проблемой, то вам нужно перестроить массив, чтобы заполнить пробелы:
for i in "${!array[@]}"; do new_array+=( "${array[i]}" ) done array=("${new_array[@]}") unset new_array
вы можете создать новый массив без нежелательного элемента, а затем назначить его обратно в старый массив. Это работает в
bash:array=(pluto pippo) new_array=() for value in "${array[@]}" do [[ $value != pluto ]] && new_array+=($value) done array=("${new_array[@]}") unset new_arrayЭто дает:
echo "${array[@]}" pippo
чтобы расширить приведенные выше ответы, можно использовать следующее Для удаления нескольких элементов из массива без частичного сопоставления:
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это приведет к массиву, содержащему: (два один два три три четыре "один шесть")
Это самый прямой способ снять значение, если вы знаете, что это позиция.
$ array=(one two three) $ echo ${#array[@]} 3 $ unset 'array[1]' $ echo ${array[@]} one three $ echo ${#array[@]} 2
скрипт оболочки POSIX не имеет массивов.
поэтому, скорее всего, вы используете определенный диалект, такой как
bash, корн снарядов илиzsh.поэтому ваш вопрос сейчас невозможно ответить.
может быть, это работает для вас:
unset array[$delete]
вот (вероятно, очень специфичная для bash) небольшая функция, включающая косвенную переменную bash и
unset; это общее решение, которое не требует замены текста или отбрасывания пустых элементов и не имеет проблем с цитированием/пробелами и т. д.delete_ary_elmt() { local word= # the element to search for & delete local aryref="[@]" # a necessary step since '${![@]}' 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 "[$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использовать его как
delete_ary_elmt ELEMENT ARRAYNAMEбез$сигил. Переключатель== $wordна== $word*для префикса; использовать${elmt,,} == ${word,,}для регистро-независимого играм; и т. д. какая бы Баш[[поддерживает.It работает путем определения индексов входного массива и итерации по ним в обратном направлении (поэтому удаление элементов не испортит порядок итерации). Чтобы получить индексы, вам нужно получить доступ к входному массиву по имени, что можно сделать с помощью переменной bash indirection
x=1; varname=x; echo ${!varname} # prints "1".вы не можете получить доступ к массивам по имени, как
aryname=a; echo "${$aryname[@]}, это дает вам ошибку. Вы не можете сделатьaryname=a; echo "${!aryname[@]}", это дает вам индексы переменнойaryname(хотя это и не массив). Что действительно работаетaryref="a[@]"; echo "${!aryref}", которым выведет элементы массиваa, сохраняя оболочку-слово цитирование и пробелы точно так же, какecho "${a[@]}". Но это работает только для печати элементов массива, а не для печати его длины или индексов (aryref="!a[@]"илиaryref="#a[@]"или"${!!aryref}"или"${#!aryref}", они все терпят неудачу).поэтому я копирую исходный массив по его имени с помощью косвенного bash и получаю индексы из копии. Для итерации по индексам в обратном порядке я использую c-стиль для цикла. Я также мог бы сделать это, получив доступ к индексы через
${!arycopy[@]}и обращая их сtac, который являетсяcatэто поворачивает вокруг порядка ввода строки.решение функции без переменной косвенности, вероятно, должно включать
eval, который может или не может быть безопасным, чтобы использовать в этой ситуации (я не могу сказать).
вот однострочное решение с mapfile:
$ mapfile -d $'' -t arr < <(printf '%s' "${arr[@]}" | grep -Pzv "<regexp>")пример:
$ 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 $'' -t arr < <(printf '%s' "${arr[@]}" | grep -Pzv "^Claire\nSmith$") $ echo "Size: ${#arr[*]} Contents: ${arr[*]}" Size: 5 Contents: Adam Bob David Eve Fredэтот метод обеспечивает большую гибкость путем изменения / обмена командой grep и не оставляет пустых строк в массиве.
На самом деле, я только что заметил, что синтаксис оболочки несколько имеет встроенное поведение, которое позволяет легко восстанавливать массив, когда, как указано в вопросе, элемент должен быть удален.
# 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обратите внимание, как мы построили массив с помощью Баша
x+=()синтаксис?вы могли бы на самом деле добавить более одного элемента с этим, содержимое целого другого массива сразу.
http://wiki.bash-hackers.org/syntax/pe#substring_removal
${параметр#PATTERN} # удалить из начала
${параметр# # PATTERN} # удалить с самого начала, жадный матч
${параметр%PATTERN} # удалить с конца
${параметр% % PATTERN} # удалить с конца, жадный матч
для того, чтобы сделать полный удалить элемент, вы должны сделать команду unset с if заявление. Если вы не заботитесь об удалении префиксов из других переменных или о поддержке пробелов в массиве, то вы можете просто отбросить кавычки и забыть о циклах for.
см. пример ниже для нескольких различных способов очистки массива.
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выход
Choose an option: 1) foobar 2) foo bar 3) s 4) Quit Option?надеюсь, что это поможет.
используя
unsetчтобы удалить элемент с определенным индексом, мы можем использовать
unsetа затем сделать копию в другой массив. Только чтоunsetв этом случае не требуется. Потому чтоunsetне удаляет элемент, он просто устанавливает нулевую строку для конкретного индекса в массиве.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]}"выход
aa cc dd ee 1st val is , 2nd val is cc aa cc dd ee 1st val is cc, 2nd val is ddиспользуя
:<idx>мы можем удалить некоторый набор элементов с помощью
:<idx>также. Для пример если мы хотим удалить 1-й элемент, мы можем использовать:1, как указано ниже.declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee') arr2=("${arr[@]:1}") echo ${arr2[@]} echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"выход
bb cc dd ee 1st val is cc, 2nd val is dd
в ZSH это очень просто (обратите внимание, что для удобства понимания используется более совместимый синтаксис bash, чем это необходимо):
# 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[@]}"результаты:
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
частичный ответ только
удалить первый элемент в массиве
unset array[0]удалить последний элемент в массиве
unset array[-1]
что я делаю это:
array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"БАМ, этот элемент удаляется.
это быстрое и грязное решение, которое будет работать в простых случаях, но сломается, если (a) есть специальные символы регулярных выражений в
$delete, или (b)есть любые пробелы вообще в любых элементах. Начиная с:array+=(pluto) array+=(pippo) delete=(pluto)удалить все записи, точно соответствующие
$delete:array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)в результате
echo $array-> Пиппо, и убедившись, что это массив:echo $array[1]-> Пиппо
fmtнемного непонятными:fmt -1обертывания в первом столбце (чтобы поместить каждый элемент на своей собственной линии. Вот где возникает проблема с предметами в пространстве.)fmt -999999разворачивает его обратно в одну строку, вернуть пробелы между элементами. Есть и другие способы сделать это, напримерxargs.добавление: если вы хотите удалить только первый матч, используйте sed, как описано здесь:
array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
Как насчет чего-то вроде:
array=(one two three) array_t=" ${array[@]} " delete=one array=(${array_t// $delete / }) unset array_t
#/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]}" = "" ] && 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=; 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=""; 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
Comments