Подтвердить что ты не робот

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

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

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

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

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

Любая идея?

4b9b3361

Ответ 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