web-dev-qa-db-ja.com

Bash配列から要素を削除する

Bashシェルの配列から要素を削除する必要があります。一般的に私は単にやる:

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

残念ながら、削除したい要素は変数なので、前のコマンドは使用できません。下の例:

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

何か案が?

79
Alex

以下は、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]} = $target ]]; then
      unset 'array[i]'
    fi
  done
done

これを行うと、1つ以上の要素が削除されると、インデックスは整数の連続したシーケンスではなくなることに注意してください。

$ 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
126
chepner

不要な要素なしで新しい配列を作成し、それを古い配列に割り当て直すことができます。これは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
24
Steve Kehlet

これは、位置がわかっている場合に値を設定解除する最も直接的な方法です。

$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2
7
signull

Mapfileを使用した1行のソリューションは次のとおりです。

$ 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コマンドを変更/交換することで柔軟性を高め、配列に空の文字列を残しません。

4
Niklas Holm

上記の答えを拡張するには、次を使用して、部分一致なしで配列から複数の要素を削除できます。

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

これにより、次のものを含む配列が生成されます:(two onetwo 3 threefour "one six")

2
Dylan

次に、bash変数の間接指定とunset;を含む(おそらく非常にbash固有の)小さな関数を示します。テキストの置換や空の要素の破棄を伴わない一般的なソリューションであり、引用符や空白などの問題はありません。

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スタイルのforループを使用します。 ${!arycopy[@]}を介してインデックスにアクセスし、tacを使用してインデックスを逆にすることでもできます。これは、入力行の順序を反転するcatです。

変数間接指定のない関数ソリューションには、おそらくevalが含まれる必要があります。これは、その状況で安全に使用できる場合とそうでない場合があります(わかりません)。

2
S.V.P.

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を使用できます。

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
1
rashok

unsetを使用して配列インデックスとの競合を回避するには- https://stackoverflow.com/a/49626928/3223785 および https://stackoverflow.com/a/47798640を参照してください/ 3223785 詳細については、配列をそれ自体に再割り当てします: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

[参照: https://tecadmin.net/working-with-array-bash-script/ ]

0
Eduardo Lucio

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
0
trevorj

POSIXシェルスクリプトには配列がありません。

そのため、おそらくbash、kornシェル、またはzshなどの特定の方言を使用しています。

したがって、今のあなたの質問には答えられません。

たぶんこれはあなたのために働く:

unset array[$delete]
0
Anony-Mousse

部分回答のみ

配列の最初のアイテムを削除するには

unset 'array[0]'

配列の最後のアイテムを削除するには

unset 'array[-1]'
0
consideRatio

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

$ {PARAMETER#PATTERN}#最初から削除

$ {PARAMETER ## PATTERN}#最初から削除する、貪欲なマッチ

$ {PARAMETER%PATTERN}#最後から削除

$ {PARAMETER %% PATTERN}#最後から削除、貪欲なマッチ

要素を完全に削除するには、ifステートメントを使用してunsetコマンドを実行する必要があります。他の変数からプレフィックスを削除したり、配列内の空白をサポートしたりする必要がない場合は、引用符を削除して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? 

それが役に立てば幸いです。

0
phyatt

実際、シェル構文には、問題で提起されたようにアイテムを削除する必要があるときに配列を簡単に再構築できるようにする動作が組み込まれていることに気づきました。

# 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

Bashのx+=()構文を使用して配列を作成した方法に注目してください。

実際には、複数のアイテムを追加できます。つまり、他の配列全体のコンテンツを一度に追加できます。

0
mar77i

この構文もあります。 2番目の要素を削除する場合:

array=("${array[@]:0:1}" "${array[@]:2}")

実際には、2つのタブが連結されています。最初はインデックス0からインデックス1(排他的)まで、2番目はインデックス2から最後まで。

0
OphyTe