Bash'deki bir diziden nasıl benzersiz değerler alabilirim?


93

Buradaki soruyla neredeyse aynı sorum var .

İçeren bir dizim var aa ab aa ac aa ad, vb. Şimdi bu diziden tüm benzersiz öğeleri seçmek istiyorum. Düşünce, bununla basit olurdu sort | uniqya da sort -udizide değişti onlar bu diğer soru da belirttiğim gibi, ama hiçbir şey ... kodudur:

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

Neyi yanlış yapıyorum?

Yanıtlar:


131

Biraz hile, ama bunu yapmalı:

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

Sıralanan benzersiz sonuçları bir diziye geri kaydetmek için Dizi ataması yapın :

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

Kabuğunuz herestring'i destekliyorsa ( bashgerekir), bir echoişlemi şu şekilde değiştirerek yedekleyebilirsiniz :

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

Giriş:

ids=(aa ab aa ac aa ad)

Çıktı:

aa ab ac ad

Açıklama:

  • "${ids[@]}"- İster echobir ringa balığı parçası olarak ister bir ringa balığı olarak kullanılsın, kabuk dizileriyle çalışmak için sözdizimi . @Parçası vasıtası "dizideki tüm elemanları"
  • tr ' ' '\n'- Tüm boşlukları yeni satırlara dönüştürün. Çünkü diziniz kabuk tarafından boşluklarla ayrılmış tek bir satırdaki elemanlar olarak görülüyor; ve çünkü sıralama, girdinin ayrı satırlarda olmasını bekler.
  • sort -u - yalnızca benzersiz öğeleri sıralayın ve koruyun
  • tr '\n' ' ' - daha önce eklediğimiz yeni satırları boşluklara dönüştürün.
  • $(...)- Komut Değiştirme
  • Kenara: tr ' ' '\n' <<< "${ids[@]}"aşağıdakileri yapmanın daha etkili bir yoludur:echo "${ids[@]}" | tr ' ' '\n'

37
+1. Biraz daha düzenli: uniq öğeleri yeni bir dizide saklayın:uniq=($(printf "%s\n" "${ids[@]}" | sort -u)); echo "${uniq[@]}"
glenn jackman

@glennjackman oh neat! printfBu şekilde kullanabileceğinizi bile fark etmemiştim (dizeleri biçimlendirmekten daha fazla argüman verin)
sampson-chen

4
+1 Bunun izole bir durum olup olmadığından emin değilim, ancak benzersiz öğeleri bir diziye geri koymak gibi ek parantezler gerekli sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')). Ek parantezler olmadan onu dizge olarak veriyordu.
2014

3
Öğelerin sırasını değiştirmek istemiyorsanız, ... | uniq | ...yerine kullanın ... | sort -u | ....
Jesse Chisholm

2
@Jesse, uniqyalnızca ardışık kopyaları kaldırır . Bu cevaptaki örnekte sorted_unique_ids, orijinal ile aynı olacak ids. Düzeni korumak için deneyin ... | awk '!seen[$0]++'. Ayrıca stackoverflow.com/questions/1444406/… konusuna bakın .
Rob Kennedy

29

Bash sürüm 4 veya üzerini çalıştırıyorsanız (Linux'un herhangi bir modern sürümünde durum böyle olmalıdır), orijinal dizinin her bir değerini içeren yeni bir ilişkilendirilebilir dizi oluşturarak bash'de benzersiz dizi değerleri elde edebilirsiniz. Bunun gibi bir şey:

$ 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

Bu işe yarar çünkü herhangi bir dizide (ilişkisel veya geleneksel, herhangi bir dilde), her anahtar yalnızca bir kez görünebilir. Zaman fordöngü ikinci değere ulaşır aain a[2], bu yazar b[aa]için, esas olarak ayarlanmış edildi a[0].

Yerli bash Uyguluyor boru ve benzeri dış araçlarını kullanarak daha hızlı olabilir sortve uniqvb awk, piton, gibi daha güçlü bir dil kullanmak durumunda daha büyük veri kümeleri için büyük olasılıkla daha iyi performans görürler,

Kendinizden emin hissediyorsanız, 'nın biçimini birden çok argüman için geri dönüştürme yeteneğini forkullanarak döngüden kaçınabilirsiniz printf, ancak bu gerektiriyor gibi görünüyor eval. (Bunda sorun yoksa şimdi okumayı bırak.)

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

Bu çözümün gerektirmesinin nedeni eval, dizi değerlerinin kelime bölünmeden önce belirlenmesidir. Bu, komut ikamesinin çıktısının bir anahtar = değer çifti kümesi yerine tek bir kelime olarak kabul edildiği anlamına gelir .

Bu bir alt kabuk kullanırken, dizi değerlerini işlemek için yalnızca bash yerleşiklerini kullanır. Kullanımınızı evaleleştirel bir gözle değerlendirdiğinizden emin olun . Chepner veya glenn jackman veya greycat'ın kodunuzda herhangi bir hata bulmayacağından% 100 emin değilseniz, bunun yerine for döngüsünü kullanın.


hata üretir: ifade yineleme seviyesi aşıldı
Benubird

1
@Benubird - belki terminal içeriklerinizi yapıştırabilir misiniz? Benim için mükemmel çalışıyor, bu yüzden en iyi tahminim (1) bir yazım hatası, (2) daha eski bir bash sürümü (v4'e ilişkilendirilebilir diziler eklendi) veya (3) gülünç derecede büyük bir kozmik arka plan akışı var. komşunuzun bodrumundaki kuantum kara deliğin neden olduğu radyasyon, bilgisayarınızdaki sinyallerde parazit oluşturur.
ghoti

1
yapamam, çalışmayan birini tutmadı. ama şimdi seninkini çalıştırmayı denedim ve işe yaradı, yani muhtemelen kozmik radyasyon olayı.
Benubird

Bu cevabın bash v4'ü (ilişkisel diziler) kullandığını tahmin etmek ve eğer birisi bash v3'ü denerse işe yaramayacaktır (muhtemelen @Benubird'ün gördüğü şey değildir). Bash v3 birçok ortamda hala varsayılan
nhed

1
@nhed, puan alındı. Macports'tan v4'ü yüklememe rağmen, güncel Yosemite Macbook'umun temelde aynı sürüme sahip olduğunu görüyorum. Bu soru "linux" olarak etiketlenmiştir, ancak gereksinimi belirtmek için cevabımı güncelledim.
ghoti

18

Bunun zaten yanıtlandığını biliyorum, ancak arama sonuçlarında oldukça yüksek bir şekilde göründü ve birine yardımcı olabilir.

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

Misal:

~> 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
diziyi düzeltmek için bunu yapmak zorunda kaldım: bu ids=(ab "a a" ac aa ad ac aa);IFS=$'\n' ids2=(`printf "%s\n" "${ids[@]}" |sort -u`)yüzden IFS=$'\n'@gniourf_gniourf tarafından önerilen ekledim
Aquarius Power

Ayrıca yedeklemek ve komuttan sonra IFS değerini geri yüklemek zorunda kaldım! ya da başka şeyleri karıştırır ..
Aquarius Power

@Jetse Bu, yalnızca iki komut kullandığından, döngü olmadığından, eval olmadığından ve en kompakt sürüm olduğundan kabul edilen yanıt olmalıdır.
mgutt

1
@AquariusPower Dikkatli olun, temelde şunu yapıyorsunuz: IFS=$'\n'; ids2=(...)çünkü değişken atamalardan önce geçici atama yapmak mümkün değil. Bunun yerine bu inşaat kullanın: IFS=$'\n' read -r -a ids2 <<<"$(printf "%s\n" "${ids[@]}" | sort -u)".
yeti

13

Dizi öğelerinizde beyaz boşluk veya başka bir kabuk özel karakteri varsa (ve olmadığından emin misiniz?), O zaman önce bunları yakalamak (ve bunu her zaman yapmalısınız) dizinizi çift tırnak içinde ifade edin! ör "${a[@]}". Bash bunu kelimenin tam anlamıyla "her bir dizi öğesi ayrı bir bağımsız değişken " olarak yorumlayacaktır . Bash içinde bu her zaman çalışır, her zaman.

Daha sonra, sıralı (ve benzersiz) bir dizi elde etmek için, onu anlayan bir biçime dönüştürmemiz ve onu tekrar bash dizi öğelerine dönüştürebilmemiz gerekir. Bulduğum en iyisi bu:

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

Ne yazık ki bu, boş dizinin özel durumunda başarısız olur ve boş diziyi 1 boş eleman dizisine dönüştürür (çünkü printf 0 argümana sahiptir, ancak yine de bir boş argümanı varmış gibi yazdırır - açıklamaya bakın). Yani bunu bir eğer veya başka bir şeyde yakalamalısın.

Açıklama: printf için% q biçimi, bash'ın eval gibi bir şeyle kurtarabilmesi gibi, yazdırılan bağımsız değişkenden "kaçar"! Her öğe kendi satırında basılmış bir kabuk olduğundan, öğeler arasındaki tek ayırıcı satırsonu ve dizi ataması, her satırı bir öğe olarak alır ve kaçan değerleri değişmez metin olarak ayrıştırır.

Örneğin

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

Değerlendirme, diziye geri dönen her bir değerden kaçışı çıkarmak için gereklidir.


Bu benim için çalışan tek koddu çünkü dizilerimde boşluklar vardı. % Q hile yapan şeydi. Teşekkürler :)
Somaiah Kumbera

Öğelerin sırasını değiştirmek istemiyorsanız, uniqyerine kullanın sort -u.
Jesse Chisholm

Bunun uniqsıralanmamış listelerde düzgün çalışmadığını, bu nedenle her zaman ile birlikte kullanılması gerektiğini unutmayın sort.
Jean Paul

sıralanmamış bir listedeki uniq, ardışık kopyaları kaldıracaktır . Aralarında başka bir şeyle ayrılmış özdeş liste öğelerini kaldırmaz. uniq, beklenen verilere ve orijinal düzeni sürdürme arzusuna bağlı olarak yeterince yararlı olabilir.
vontrapp

10

'sort' bir for-döngüsünün çıktısını sıralamak için kullanılabilir:

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

ve "-u" ile yinelenenleri eleyin:

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

Son olarak, benzersiz öğelerle dizinizin üzerine yazabilirsiniz:

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

Kalanların sırasını değiştirmek istemiyorsanız, yapmanız gerekenler:ids=( `for i in ${ids[@]}; do echo $i; done | uniq` )
Jesse Chisholm

3

bu da düzeni koruyacaktır:

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

ve orijinal diziyi benzersiz değerlerle değiştirmek için:

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

Kullanmayın uniq. Awk'nin olmadığı yerlerde sıralama gerekir ve bu cevabın amacı, girdi sıralanmadığında sıralamayı korumaktır.
bukzor

2

Benzersiz değerlerden oluşan yeni bir dizi oluşturmak için dizinizin boş olmadığından emin olun ve ardından aşağıdakilerden birini yapın:

Yinelenen girişleri kaldırın (sıralama ile)

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

Yinelenen girişleri kaldırın (sıralamadan)

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

Uyarı: Böyle bir şey yapmaya çalışmayın NewArray=( $(printf '%s\n' "${OriginalArray[@]}" | sort -u) ). Boşluklarda kırılacak.


Değişim hariç (sıralama ile) gibi bulun (sıralama olmadan) yinelenen girdileri kaldırın sort -uolmak uniq.
Jesse Chisholm

@JesseChisholm, uniqyalnızca bitişik olan yinelenen satırları birleştirir, bu yüzden aynı değildir awk '!x[$0]++'.
Altı

@JesseChisholm Lütfen yanıltıcı yorumu silin.
bukzor

2

kedi numarası.txt

1 2 3 4 4 3 2 5 6

satırı sütuna yazdır: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}'

1
2
3
4
4
3
2
5
6

yinelenen kayıtları bulun: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++'

4
3
2

Yinelenen kayıtları değiştirin: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++'

1
2
3
4
5
6

Yalnızca Uniq kayıtlarını bulun: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"}

1
5
6

1

Orijinal sıralamayı kaybetmeden:

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

1

Yalnızca dahili bash kullanan bir çözüm istiyorsanız, değerleri ilişkilendirilebilir bir dizideki anahtarlar olarak ayarlayabilir ve ardından anahtarları çıkarabilirsiniz:

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

Bu çıktı verecek

bar
foo
bar none

Bunun esasen yukarıdaki @ghotis cevabıyla aynı olduğunu fark ettim, ancak çözümü boşluklu liste öğelerini hesaba katmıyor.
2017

İyi bir nokta. Çözümüme alıntılar ekledim, böylece artık boşlukları ele alıyor. Aslında bunu sadece sorudaki örnek verileri işlemek için yazdım, ancak bunun gibi olasılıkları kapsamak her zaman iyidir. Önerin için teşekkürler.
ghoti

1

Gömülü boşluklarla uğraşmak için başka bir seçenek, null-sınırlandırmak printf, ayırt etmek sort, sonra onu bir diziye geri paketlemek için bir döngü kullanmaktır:

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)

Bunun sonunda inputve outputistenen değerleri ekleyin (verilen sıra önemli değildir):

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

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

1

Bu varyasyona ne dersiniz?

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

Ve sonra sorted_arr=($(printf '%s\n' "${ids[@]}" | sort -u).
yosun

0

Dosyadaki ilk sütun için benzersiz değerler elde etmek için bunu deneyin

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.