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


93

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

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

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

私は何が間違っているのですか?

回答:


131

少しハッキーですが、これでうまくいくはずです:

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

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

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

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

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

入力:

ids=(aa ab aa ac aa ad)

出力:

aa ab ac ad

説明:

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

37
+1。少し整理uniq=($(printf "%s\n" "${ids[@]}" | sort -u)); echo "${uniq[@]}"
整頓

@glennjackmanおやおや!私はあなたがprintfそのように使うことができることにさえ気づいていませんでした(フォーマット文字列より多くの引数を与えてください)
sampson-chen 2012

4
+1これが孤立したケースかどうかはわかりませんが、一意のアイテムを配列に戻すには、次のような追加の括弧が必要でしたsorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))。追加の括弧なしで、それは文字列としてそれを与えていました。
whla 2014年

3
要素の順序を変更したくない場合は、の... | uniq | ...代わりにを使用してください... | sort -u | ...
ジェシーチザム2016

2
@Jesse、連続する重複uniqのみを削除します。この回答の例では、は元のと同じになります。順序を維持するには、を試してください。stackoverflow.com/questions/1444406/…も参照してください。sorted_unique_idsids... | awk '!seen[$0]++'
ロブ・ケネディ

29

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ループの第二の値に到達するaaa[2]は、上書きb[aa]のために元々設定されましたa[0]

ネイティブbashでの作業はsort、パイプややなどの外部ツールを使用するよりも高速ですuniqが、データセットが大きい場合は、awkやpythonなどのより強力な言語を使用するとパフォーマンスが向上する可能性があります。

自信がある場合は、の機能を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とする理由は、単語分割の前に配列値が決定されるためです。つまり、コマンド置換の出力は、キーと値のペアのセットではなく、単一の単語と見なされます。

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


エラーが発生します:式の再帰レベルを超えました
Benubird 2014

1
@ Benubird-端末のコンテンツをペーストビン化できますか?それは私にとって完璧に機能するので、(1)タイプミス、(2)古いバージョンのbash(連想配列がv4に追加された)、または(3)途方もなく大量の宇宙背景放射があると推測されます。あなたの隣人の地下室の量子ブラックホールによって引き起こされた放射線は、あなたのコンピュータ内の信号との干渉を生成します。
ghoti 2014

1
できません、動作しなかったものを保持しませんでした。しかし、私はちょうど今あなたを走らせてみました、そしてそれはうまくいきました、それでおそらく宇宙線のこと。
ベヌバード2014

この回答はbashv4(連想配列)を利用しており、誰かがbash v3を試してみると、機能しないと推測します(おそらく、@ Benubirdが見たものではありません)。Bash v3は、多くの環境でまだデフォルトです
2015

1
@nhed、ポイントを取った。Macportsからv4をインストールしましたが、最新のYosemiteMacbookのベースは同じバージョンであることがわかります。この質問には「linux」というタグが付けられていますが、要件を指摘するために回答を更新しました。
ghoti 2015

18

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

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
~>

1
アレイを修正するために、私はこれを強制されました:ids=(ab "a a" ac aa ad ac aa);IFS=$'\n' ids2=(`printf "%s\n" "${ids[@]}" |sort -u`)、それで私IFS=$'\n'は@gniourf_gniourfによって提案されたものを追加しました
Aquarius Power

また、バックアップを取り、コマンドの後にIFS値を復元する必要がありました。あるいはそれは他のものを台無しに...
アクエリアスパワー

@Jetseこれは、2つのコマンドのみを使用し、ループも評価も使用せず、最もコンパクトなバージョンであるため、受け入れられる回答になるはずです。
mgutt

1
@AquariusPower注意してください、あなたは基本的に次のことをしています:IFS=$'\n'; ids2=(...)変数割り当ての前の一時的な割り当ては不可能だからです。代わりに、次の構造を使用してくださいIFS=$'\n' read -r -a ids2 <<<"$(printf "%s\n" "${ids[@]}" | sort -u)"
イエティ

13

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

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

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

残念ながら、これは空の配列の特殊なケースでは失敗し、空の配列を1つの空の要素の配列に変えます(printfには0の引数がありましたが、それでも1つの空の引数があるかのように出力します-説明を参照してください)。したがって、ifまたは何かでそれをキャッチする必要があります。

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

例えば

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

evalは、配列に戻る各値からエスケープを取り除くために必要です。


文字列の配列にスペースが含まれていたため、これが私のために機能した唯一のコードです。%qはトリックをしたものです。ありがとう:)
Somaiah Kumbera 2015年

また、要素の順序を変更したくない場合は、のuniq代わりにを使用してくださいsort -u
ジェシーチザム2016

uniqソートされていないリストでは正しく機能しないため、常にと組み合わせて使用​​する必要があることに注意してくださいsort
ジャンポール

ソートされていないリストのuniqは、連続する重複を削除します。間にある他の何かで区切られた同一のリスト要素は削除されません。uniqは、期待されるデータと元の順序を維持したいという要望によっては、十分に役立つ場合があります。
vontrapp

10

'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` )

:あなたが残っているものの順序を変更したくない場合は、あなたがする必要はありませんids=( `for i in ${ids[@]}; do echo $i; done | uniq` )
ジェシー・チザム

3

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

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

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

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

使用しないでくださいuniq。awkが必要としないソートが必要であり、この回答の目的は、入力がソートされていないときに順序を保持することです。
bukzor

2

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

重複するエントリを削除する(並べ替えを使用)

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) )。それはスペースで壊れます。


重複するエントリの削除(並べ替えなし)は、(並べ替えあり)と同じですが、に変更さsort -uれますuniq
ジェシーチザム2016

@JesseChisholmuniqは、隣接する重複行のみをマージするため、と同じではありませんawk '!x[$0]++'
2016

@JesseChisholm誤解を招くコメントを削除してください。
bukzor

2

猫番号.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

元の注文を失うことなく:

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

1

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

彼のソリューションがスペースを含むリストアイテムを考慮に入れていないことを除いて、これは上記の@ghotisの回答と本質的に同じであることに気づきました。
rln 2017年

いい視点ね。ソリューションに引用符を追加して、スペースを処理できるようにしました。私はもともと質問のサンプルデータを処理するためだけに書いたのですが、このような不測の事態をカバーすることは常に良いことです。提案をありがとう。
ghoti 2017

1

埋め込まれた空白を処理するための別のオプションは、でnull区切り、printfで区別しsort、ループを使用して配列にパックすることです。

input=(a b c "$(printf "d\ne")" b c "$(printf "d\ne")")
output=()

while read -rd $'' element
do 
  output+=("$element")
done < <(printf "%s\0" "${input[@]}" | sort -uz)

この終わりに、input及びoutput所望の値を含む(但し、順序は重要ではありません)。

$ printf "%q\n" "${input[@]}"
a
b
c
$'d\ne'
b
c
$'d\ne'

$ printf "%q\n" "${output[@]}"
a
b
c
$'d\ne'

1

このバリエーションはどうですか?

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

そしてsorted_arr=($(printf '%s\n' "${ids[@]}" | sort -u)
藻類

0

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

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

-3
# Read a file into variable
lines=$(cat /path/to/my/file)

# Go through each line the file put in the variable, and assign it a variable called $line
for line in $lines; do
  # Print the line
  echo $line
# End the loop, then sort it (add -u to have unique lines)
done | sort -u
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.