web-dev-qa-db-ja.com

Bashの配列から一意の値を取得するにはどうすればよいですか?

here とほぼ同じ質問があります。

aa ab aa ac aa adなどを含む配列があります。次に、この配列からすべての一意の要素を選択します。他の質問で述べたように、これはsort | uniqまたはsort -uで簡単になりますが、配列では何も変わりません...コードは次のとおりです。

echo `echo "${ids[@]}" | sort | uniq`

何が間違っていますか?

69
Jetse

少しハッキーですが、これはそれを行う必要があります:

_echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
_

並べ替えられた一意の結果を配列に保存するには、 Array assignment を実行します。

_sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
_

シェルが herestringsbash should)をサポートしている場合、echoプロセスを次のように変更することにより、プロセスを節約できます。

_tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '
_

入力:

_ids=(aa ab aa ac aa ad)
_

出力:

_aa ab ac ad
_

説明:

  • _"${ids[@]}"_-echoまたはherestringの一部として使用されるかどうかに関係なく、シェル配列を操作するための構文。 _@_部分は、「配列内のすべての要素」を意味します
  • _tr ' ' '\n'_-すべてのスペースを改行に変換します。配列は、スペースで区切られた単一行の要素としてシェルによって認識されるため、また、sortは入力が別々の行にあることを想定しているためです。
  • _sort -u_-一意の要素のみを並べ替えて保持する
  • _tr '\n' ' '_-前に追加した改行をスペースに戻します。
  • $(...)- コマンド置換
  • 余談:_tr ' ' '\n' <<< "${ids[@]}"_はより効率的な方法です:_echo "${ids[@]}" | tr ' ' '\n'_
103
sampson-chen

Bashバージョン4以上を実行している場合(Linuxの最新バージョンの場合)、元の配列の各値を含む新しい連想配列を作成することにより、bashで一意の配列値を取得できます。このようなもの:

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

これは、配列内で各キーが1回しか表示されないために機能します。 forループがa[2]aaの2番目の値に到達すると、b[aa]に元々設定されていたa[0]を上書きします。

ネイティブbashでの処理は、パイプやsortuniqなどの外部ツールを使用するよりも高速です。

自信がある場合は、forが必要なようですが、printfの複数の引数の形式をリサイクルする機能を使用して、evalループを回避できます。 (それでよければ、今すぐ読んでください。)

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

このソリューションがevalを必要とする理由は、配列値がWord分割の前に決定されるためです。つまり、コマンド置換の出力は、key = valueのペアのセットではなく、単一のWordと見なされます。

これはサブシェルを使用しますが、bashビルトインのみを使用して配列値を処理します。 evalの使用を批判的な目で必ず評価してください。 chepnerまたはglenn jackmanまたはgreycatがコードに問題がないことを100%確信していない場合は、代わりにforループを使用します。

22
ghoti

これはすでに回答済みであることがわかりますが、検索結果でかなり多く表示され、誰かを助けるかもしれません。

printf "%s\n" "${IDS[@]}" | sort -u

例:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>
12
das.cyklone

配列要素に空白またはその他のシェル特殊文字が含まれている場合(そうでないことを確認できますか?)、まずそれらをキャプチャするには(そして、常にこれを行う必要があります)、配列を二重引用符で表現します!例えば"${a[@]}"。 Bashはこれを文字通り「個別のargument "の各配列要素」と解釈します。 bash内では、これは常に常に機能します。

次に、並べ替えられた(一意の)配列を取得するには、並べ替えが理解できる形式に変換し、bash配列要素に戻す必要があります。これは私が思いついた最高のものです:

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

残念ながら、これは空の配列の特別な場合に失敗し、空の配列を1つの空の要素の配列に変換します(printfには引数がありませんが、空の引数が1つあるように印刷されます-説明を参照)。したがって、ifまたは何かでそれをキャッチする必要があります。

説明:printfの%q形式は、bashがevalのようなもので回復できるような方法で、印刷された引数を「シェルエスケープ」します!各要素は独自の行にエスケープされたシェルで印刷されるため、要素間の唯一のセパレータは改行であり、配列の割り当ては各行を要素として、エスケープされた値をリテラルテキストに解析します。

例えば.

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

Evalは、配列に戻る各値をエスケープするために必要です。

11
vontrapp

'sort'を使用してforループの出力を順序付けることができます。

for i in ${ids[@]}; do echo $i; done | sort

「-u」を使用して重複を削除します。

for i in ${ids[@]}; do echo $i; done | sort -u

最後に、配列を一意の要素で上書きできます。

ids=( `for i in ${ids[@]}; do echo $i; done | sort -u` )
7
corbyn42

これも順序を保持します:

echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'

一意の値で元の配列を変更するには:

ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))
2
faustus

一意の値で構成される新しい配列を作成するには、配列が空でないことを確認してから、次のいずれかを実行します。

重複したエントリを削除する(並べ替えあり)

_readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | sort -u)
_

重複したエントリを削除します(並べ替えなし)

_readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | awk '!x[$0]++')
_

警告:NewArray=( $(printf '%s\n' "${OriginalArray[@]}" | sort -u) )のようなことをしようとしないでください。スペースで中断します。

2
Six

猫番号.txt

_1 2 3 4 4 3 2 5 6
_

列に行を出力:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}'

_1
2
3
4
4
3
2
5
6
_

重複レコードを見つけます:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++'

_4
3
2
_

重複するレコードを置き換えます:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++'

_1
2
3
4
5
6
_

Uniqレコードのみを検索:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"}

_1
5
6
_
1
VIPIN KUMAR

Bash内部のみを使用するソリューションが必要な場合は、値を連想配列のキーとして設定し、キーを抽出できます。

declare -A uniqs
list=(foo bar bar "bar none")
for f in "${list[@]}"; do 
  uniqs["${f}"]=""
done

for thing in "${!uniqs[@]}"; do
  echo "${thing}"
done

これは出力します

bar
foo
bar none
1
rln

これを試して、ファイルの最初の列の一意の値を取得します

awk -F, '{a[$1];}END{for (i in a)print i;}'
0
Suresh Aitha

元の順序を失うことなく:

uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))
0
estani