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


116

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

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

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

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

何か案が?


どのシェル?あなたの例は次のようになりzshます。
chepner 2013年

array=( ${array[@]/$delete} )バッシュで期待どおりに動作します。あなたは単に見逃しました=か?
Ken Sharp

1
@ケン、それはまったく望んでいることではありません-各文字列から一致を削除し、文字列全体と一致する配列に空の文字列を残します。
Toby Speight

回答:


165

あなたが希望として、次の作品bashzsh

$ 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

43
ちょうどそれを知っている:$ array=(sun sunflower) $ delete=(sun) $ echo ${array[@]/$delete}結果はflower
14:41でbernstein 14年

12
これは実際に置換を行っていることに注意してください。そのため、配列がそのようなものである場合、結果はに(pluto1 pluto2 pippo)なります(1 2 pippo)
haridsv 2014

5
forループでこれを使用する場合は注意してください。削除された要素があった場所に空の要素ができてしまうからです。正気のためにあなたは次のようなことをすることができますfor element in "${array[@]}" do if [[ $element ]]; then echo ${element} fi done
Joel B

2
では、一致する要素のみを削除するにはどうすればよいですか?
UmaN 2016

4
注:これにより、それぞれの値が何も設定されない場合がありますが、要素は配列に残ります。
phil294 2016年

29

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

14

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

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

3
を試してくださいecho ${array[1]}。ヌル文字列を取得します。そして、threeあなたがする必要があるようにするためにecho ${array[2]}。そのunsetため、bash配列の要素を削除する適切なメカニズムではありません。
rashok

@rashok、いいえ、${array[1]+x}null文字列なのでarray[1]設定されていません。unset残りの要素のインデックスは変更しません。unsetの引数を引用する必要はありません。配列要素を破棄する方法は、Bashマニュアルに記載されています
jarno

@rashok理由がわかりません。${array[1]}サイズが2であるだけでは存在するとは限りません${!array[@]}。インデックスが必要な場合は、を確認してください。
ダニエルC.ソブラル

4

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コマンドを変更/交換することで大きな柔軟性が得られ、配列に空の文字列が残ることはありません。


1
printf '%s\n' "${array[@]}"その醜いIFS/ echoモノの代わりに使ってください。
gniourf_gniourf 2017年

これは、改行を含むフィールドでは失敗することに注意してください。
gniourf_gniourf 2017年

@Socowi少なくともbash 4.4.19では不正解です。引数なしでは-d $'\0'うまく-dいきませんが、完璧に機能します。
Niklas Holm

ああ、そうです。ごめんなさい。私が言ったのは:-d $'\0'と同じ-d $'\0 something'か、それともです-d ''
ソコウィ

$'\0'わかりやすくするために使用しても問題ありません
Niklas Holm

4

この回答は、パフォーマンスが重要な大きな配列から複数の値を削除する場合に固有です。

最も投票数の多いソリューションは、(1)配列のパターン置換、または(2)配列要素の反復です。1つ目は高速ですが、異なる接頭辞を持つ要素のみを処理できます。2つ目はO(n * k)、n =配列サイズ、k =削除する要素です。連想配列は比較的新しい機能であり、質問が最初に投稿されたときには一般的ではなかった可能性があります。

完全一致の場合、nとkが大きいため、パフォーマンスをO(n k)からO(n + k log(k))に改善できます。実際には、O(n)はkがnよりはるかに低いと仮定しています。高速化のほとんどは、連想配列を使用して削除するアイテムを識別することに基づいています。

パフォーマンス(n配列サイズ、削除するk値)。ユーザー時間の秒単位のパフォーマンス測定

   N     K     New(seconds) Current(seconds)  Speedup
 1000   10     0.005        0.033             6X
10000   10     0.070        0.348             5X
10000   20     0.070        0.656             9X
10000    1     0.043        0.050             -7%

予想どおり、current解はN * Kに対して線形であり、fast解はKに対して実質的に線形であり、定数ははるかに小さくなります。fast溶液は、対わずかに遅くなりcurrent、追加のセットアップによる溶液ときK = 1、。

「高速」ソリューション:array =入力のリスト、delete =削除する値のリスト。

        declare -A delk
        for del in "${delete[@]}" ; do delk[$del]=1 ; done
                # Tag items to remove, based on
        for k in "${!array[@]}" ; do
                [ "${delk[${array[$k]}]-}" ] && unset 'array[k]'
        done
                # Compaction
        array=("${array[@]}")

current最も投票された答えから、ソリューションに対してベンチマーク。

    for target in "${delete[@]}"; do
        for i in "${!array[@]}"; do
            if [[ ${array[i]} = $target ]]; then
                unset 'array[i]'
            fi
        done
    done
    array=("${array[@]}")

3

これは(おそらく非常に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変数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[@]"or "${!!aryref}"または"${#!aryref}"、すべて失敗します)。

そのため、bashインダイレクションを介して元の配列をその名前でコピーし、コピーからインデックスを取得します。インデックスを逆に反復するには、Cスタイルのforループを使用します。私はまた、経由インデックスにアクセスすることにより、それを行うこと${!arycopy[@]}でそれらをし、逆転tacされた、cat入力ラインの順回ること。

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


これはほぼ問題なく動作しますが、関数に渡された初期配列は再宣言されないため、初期配列には値がありませんが、インデックスがめちゃくちゃになっています。つまり、同じ配列でdelete_ary_elmtを次に呼び出すと、機能しません(または間違ったものを削除します)。たとえば、貼り付けた後delete_ary_elmt "d" array、アレイを実行してから再印刷してみてください。間違った要素が削除されることがわかります。最後の要素を削除しても機能しません。
スコット

2

上記の答えをさらに詳しく説明するために、次のものを使用して、部分一致せずに配列から複数の要素を削除できます。

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

これにより、次を含む配列になります:(2 onetwo 3 threefour "one six")



1

部分的な回答のみ

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

unset 'array[0]'

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

unset 'array[-1]'

@gniourf_gniourfの引数に引用符を使用する必要はありませんunset
jarno

2
@jarno:これらの引用符を使用する必要があります。array0現在のディレクトリで名前が付けられたファイルがある場合、array[0]はglobなので、最初にarray0unsetコマンドの前に展開されます。
gniourf_gniourf

@gniourf_gniourf正解です。これは、「unset name [subscript]は添字添字の配列要素を破棄する」と現在述べているBashリファレンスマニュアルで修正する必要があります。
jarno

1

使用する 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

0

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

したがって、おそらくbash、Kornシェルまたはのような特定の方言を使用していますzsh

したがって、現時点でのご質問にはお答えできません。

多分これはあなたのために働く:

unset array[$delete]

2
こんにちは、bash shell atmを使用しています。「$ delete」は要素の位置ではなく、文字列そのものです。したがって、「設定解除」が機能するとは思わない
アレックス

0

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

# 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

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

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

すみません、試してみました。連想配列のzshでは機能しませんでした
Falk

それは問題なく動作し、私はそれをテストしました(もう一度)。うまくいかないことはありますか?うまくいかなかった点をできるだけ詳しく説明してください。どのZSHバージョンを使用していますか?
trevorj

0

この構文もあります。たとえば、2番目の要素を削除する場合は、次のようにします。

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

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



-1

これは簡単なケースで機能する素早い解決策ですが、(a)に正規表現の特殊文字がある$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。1行に折り返して、アイテム間のスペースを元に戻します。これを行うには、他にも方法があります。xargs

補遺:最初の一致のみを削除する場合は、ここで説明するようにsedを使用します

array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)

-1

次のようなものはどうですか:

array=(one two three)
array_t=" ${array[@]} "
delete=one
array=(${array_t// $delete / })
unset array_t

-1

を使用して配列インデックスとの競合を回避するには、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/ ]


-2
#/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

1
回答についてコメントするためのコメントや説明を追加できますか?
マイケル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.