Удалить элемент из массива Bash
Мне нужно удалить элемент из массива в оболочке bash.
Обычно я просто делал:
array=("${(@)array:#<element to remove>}")
К сожалению, элемент, который я хочу удалить, является переменной, поэтому я не могу использовать предыдущую команду.
Здесь приведен пример:
array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}
Любая идея?
Ответы
Ответ 1
Следующее работает так, как вы хотели бы в 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
Caveat
Этот метод фактически удаляет префиксы, соответствующие $delete
из элементов, не обязательно целых элементов.
Обновление
Чтобы действительно удалить точный элемент, вам нужно пройтись по массиву, сравнить цель с каждым элементом и использовать unset
для удаления точного соответствия.
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
Обратите внимание, что если вы сделаете это, и один или несколько элементов будут удалены, индексы больше не будут представлять собой непрерывную последовательность целых чисел.
$ 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
Ответ 2
Вы можете создать новый массив без нежелательного элемента, а затем вернуть его обратно в старый массив. Это работает в 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
Ответ 3
Это самый прямой способ сбросить значение, если вы знаете его положение.
$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2
Ответ 4
Вы можете установить элемент, который хотите уничтожить, в пустую строку, он будет считаться удаленным.
В следующем примере показано, как удалить строку b
в массиве a b c d
:
$ array=(a b c d)
$ echo ${array[@]}
a b c d
$ for i in ${!array[@]} ; do # iterate over array indexes
if [ ${array[$i]} = 'b' ] ; then # if it the value you want to delete
array[$i]='' # set the string as empty at this specific index
fi
done
$ echo ${array[@]}
a c d
Обратите внимание, что это решение работает даже в оболочке POSIX, например sh.
Ответ 5
Вот однострочное решение с mapfile:
$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${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 $'\0' -t arr < <(printf '%s\0' "${arr[@]}" | grep -Pzv "^Claire\nSmith$")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 5 Contents: Adam Bob David Eve Fred
Этот метод обеспечивает большую гибкость, изменяя/заменяя команду grep, и не оставляет пустых строк в массиве.
Ответ 6
Здесь (возможно, очень bash -специфическая) небольшая функция, включающая bash переменную косвенность и unset
; это общее решение, которое не включает в себя замену текста или отбрасывание пустых элементов и не имеет проблем с цитированием/пробелами и т.д.
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
Используйте его как delete_ary_elmt ELEMENT ARRAYNAME
без сигила $
. Переключите == $word
для == $word*
для совпадений префикса; используйте ${elmt,,} == ${word,,}
для нечувствительных к регистру совпадений; и т.д., поддерживает bash [[
.
Он работает, определяя индексы входного массива и итерации по ним назад (поэтому удаление элементов не приводит к неправильному порядку). Чтобы получить индексы, вам нужно получить доступ к массиву ввода по имени, что можно сделать с помощью bash переменной косвенности 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
, который может быть или не быть безопасным для использования в этой ситуации (я не могу сказать).
Ответ 7
Чтобы развернуть приведенные выше ответы, можно удалить следующие элементы из массива без частичного соответствия:
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
Это приведет к массиву, содержащему:
(два на три три четыре "шесть" )
Ответ 8
Использование 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
Ответ 9
В оболочке POSIX script нет массивов.
Поэтому, скорее всего, вы используете определенный диалект, например bash
, оболочки korn или zsh
.
Поэтому на ваш вопрос на данный момент не может быть дан ответ.
Возможно, это сработает для вас:
unset array[$delete]
Ответ 10
В 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
Ответ 11
http://wiki.bash-hackers.org/syntax/pe#substring_removal
${PARAMETER # PATTERN} # удалить с начала
${PARAMETER ## PATTERN} # удалить с начала, жадное соответствие
${PARAMETER% PATTERN} # удалить с конца
${PARAMETER %% PATTERN} # удалить с конца, жадное соответствие
Чтобы сделать полный элемент удаления, вам нужно выполнить команду unset с инструкцией if. Если вам не нужно удалять префиксы из других переменных или поддерживать пробелы в массиве, вы можете просто отказаться от кавычек и забыть о циклах.
См. пример ниже для нескольких различных способов очистки массива.
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?
Надеюсь, что это поможет.
Ответ 12
Фактически, я только заметил, что синтаксис оболочки имеет встроенное поведение, которое позволяет легко восстановить массив, когда, как задано в вопросе, элемент должен быть удален.
# let 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
Обратите внимание, как мы построили массив с помощью синтаксиса bash x+=()
?
Фактически вы можете добавить более одного элемента с содержимым всего другого массива сразу.
Ответ 13
Частичный ответ только
Удалить первый элемент в массиве
unset 'array[0]'
Удалить последний элемент в массиве
unset 'array[-1]'
Ответ 14
Существует также этот синтаксис, например, если вы хотите удалить 2-й элемент:
array=("${array[@]:0:1}" "${array[@]:2}")
что на самом деле является объединение 2 вкладок. Первый от индекса 0 до индекса 1 (исключая) и 2-й от индекса 2 до конца.
Ответ 15
Чтобы избежать конфликтов с индексом массива с помощью unset
- см. fooobar.com/questions/133430/... и fooobar.com/questions/133430/... для получения дополнительной информации - переназначьте массив себе: 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/ ]
Ответ 16
Что я делаю:
array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"
BAM, этот элемент удален.
Ответ 17
Это быстрое и грязное решение, которое будет работать в простых случаях, но будет ломаться, если (а) есть специальные символы регулярных выражений в $delete
или (b) в каких-либо элементах есть какие-либо пробелы. Начиная с:
array+=(pluto)
array+=(pippo)
delete=(pluto)
Удалить все записи, точно соответствующие $delete
:
array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)
в результате чего
echo $array
→ pippo, и убедитесь, что это массив:
echo $array[1]
→ pippo
fmt
немного неясен: fmt -1
обертывается в первом столбце (чтобы поместить каждый элемент в свою строку. Это там, где проблема возникает с элементами в пробелах.) fmt -999999
разворачивает его обратно в одну строку, откладывая промежутки между элементами. Существуют и другие способы сделать это, например xargs
.
Приложение: если вы хотите удалить только первое совпадение, используйте sed, как описано здесь:
array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
Ответ 18
Как насчет чего-то типа:
array=(one two three)
array_t=" ${array[@]} "
delete=one
array=(${array_t// $delete / })
unset array_t
Ответ 19
#/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
Ответ 20
Удаляет ТОЛЬКО то, что соответствует вашему элементу:
[email protected]:~$ itm="two" #Item to be deleted
[email protected]:~$ n=(one two three twentytwo) #The array of items
[email protected]:~$ n=(`echo ${n[@]} | sed -r "s/\<${itm}\>//g"`)
[email protected]:~$ echo ${n[@]}
one three twentytwo