Удаление элемента из массива Bash



мне нужно удалить элемент из массива в оболочке bash.
Вообще я бы просто сделал:



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


к сожалению, элемент, который я хочу удалить, является переменной, поэтому я не могу использовать предыдущую команду.
Вот пример:



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


есть идеи?

845   16  

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

    Ничего не найдено.