Pilih nilai unik atau berbeda dari daftar dalam skrip shell UNIX


238

Saya memiliki skrip ksh yang mengembalikan daftar nilai yang panjang, baris baru dipisahkan, dan saya hanya ingin melihat nilai unik / berbeda. Apakah mungkin melakukan ini?

Misalnya, keluaran saya adalah sufiks file dalam direktori:

tar
gz
java
gz
java
tar
class
class

Saya ingin melihat daftar seperti:

tar
gz
java
class

Jawaban:


432

Anda mungkin ingin melihat uniqdan sortaplikasi.

./yourscript.ksh | sortir | uniq

(FYI, ya, semacam itu diperlukan di baris perintah ini, uniqhanya strip duplikat baris yang segera setelah satu sama lain)

EDIT:

Bertentangan dengan apa yang telah diposting oleh Aaron Digulla sehubungan dengan uniqopsi commandline:

Diberikan input berikut:

kelas
botol
botol
botol
tempat sampah
tempat sampah
Jawa

uniq akan menampilkan semua lini tepat sekali:

kelas
botol
tempat sampah
Jawa

uniq -d akan menampilkan semua baris yang muncul lebih dari sekali, dan itu akan mencetaknya sekali:

botol
tempat sampah

uniq -u akan menampilkan semua baris yang muncul tepat sekali, dan itu akan mencetaknya sekali:

kelas
Jawa

2
Hanya FYI untuk pendatang baru: Jawaban @ AaronDigulla telah diperbaiki.
mklement0

2
titik yang sangat bagus `semacam ini diperlukan di baris perintah ini, hanya uniq strip duplikat yang segera setelah satu sama lain` yang baru saja saya pelajari !!
HattrickNZ

4
GNU sortmenampilkan -uversi untuk memberikan nilai unik juga.
Arthur2e5

Saya tahu bahwa uniqjahitan hanya memproses garis yang berdekatan (setidaknya secara default) yang berarti seseorang dapat sortmemasukkan sebelum makan uniq.
Stphane

85
./script.sh | sort -u

Ini sama dengan jawaban monoksida , tetapi sedikit lebih ringkas.


6
Anda bersikap sederhana: solusi Anda juga akan berkinerja lebih baik (mungkin hanya terlihat dengan set data besar).
mklement0

Saya pikir itu harus lebih efisien daripada ... | sort | uniqkarena dilakukan dalam satu kesempatan
Adrian Antunez

10

Untuk kumpulan data yang lebih besar di mana penyortiran mungkin tidak diinginkan, Anda juga dapat menggunakan skrip perl berikut:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

Ini pada dasarnya hanya mengingat setiap output baris sehingga tidak menampilkannya lagi.

Ini memiliki keunggulan dibandingkan sort | uniqsolusi " " karena tidak diperlukan penyortiran di muka.


2
Perhatikan bahwa penyortiran file yang sangat besar bukan masalah per se dengan sort; dapat mengurutkan file yang lebih besar dari RAM + swap yang tersedia. Perl, OTOH, akan gagal jika hanya ada beberapa duplikat.
Aaron Digulla

1
Ya, ini merupakan trade-off tergantung pada data yang diharapkan. Perl lebih baik untuk dataset besar dengan banyak duplikat (tidak diperlukan penyimpanan berbasis disk). Dataset besar dengan beberapa duplikat harus menggunakan sort (dan penyimpanan disk). Dataset kecil dapat digunakan. Secara pribadi, saya akan mencoba Perl pertama, beralih untuk mengurutkan jika gagal.
paxdiablo

Karena sort hanya memberi Anda manfaat jika harus bertukar ke disk.
paxdiablo

5
Ini bagus ketika saya ingin kemunculan pertama setiap baris. Penyortiran akan merusak itu.
Bluu

10

Dengan zsh Anda dapat melakukan ini:

% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

Atau Anda dapat menggunakan AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class

2
Solusi cerdas yang tidak melibatkan penyortiran input. Peringatan: Solusi yang sangat pintar tapi samar awk(lihat stackoverflow.com/a/21200722/45375 untuk penjelasan) akan bekerja dengan file besar selama jumlah garis unik cukup kecil (karena garis unik disimpan dalam memori ). The zshsolusi membaca seluruh file ke dalam memori pertama, yang mungkin tidak menjadi pilihan dengan file besar. Juga, seperti yang ditulis, hanya garis-garis tanpa ruang tertanam yang ditangani dengan benar; untuk memperbaikinya, gunakan IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}saja.
mklement0

Benar. Atau:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Dimitre Radoulov

1
Terima kasih, itu lebih sederhana (dengan asumsi Anda tidak perlu mengatur variabel yang diperlukan di luar subkulit). Saya ingin tahu kapan Anda membutuhkan [@]akhiran untuk referensi semua elemen array - tampaknya - setidaknya pada versi 5 - berfungsi tanpa itu; atau apakah Anda hanya menambahkannya untuk kejelasan?
mklement0

1
@ mklement0, kamu benar! Saya tidak memikirkannya ketika saya menulis posting. Sebenarnya, ini sudah cukup:print -l "${(fu)$(<infile)}"
Dimitre Radoulov

1
Fantastis, terima kasih telah memperbarui posting Anda - Saya mengambil kebebasan untuk memperbaiki awkoutput sampel juga.
mklement0

9

Pipa mereka melalui sortdan uniq. Ini menghapus semua duplikat.

uniq -dhanya memberikan duplikat, uniq -uhanya memberikan yang unik (duplikat strip).


Harus mengurutkan terlebih dahulu oleh kelihatannya
Brabster

1
Ya, benar. Atau lebih tepatnya, Anda perlu mengelompokkan semua garis duplikat bersama. Penyortiran melakukan ini menurut definisi;)
Matthew Scharley

Juga, uniq -uBUKAN perilaku default (lihat edit dalam jawaban saya untuk detail)
Matthew Scharley

7

Dengan AWK yang dapat Anda lakukan, saya merasa lebih cepat daripada menyortir

 ./yourscript.ksh | awk '!a[$0]++'

Itu pasti cara favorit saya untuk melakukan pekerjaan itu, terima kasih banyak! Khusus untuk file yang lebih besar, jenis | uniq-solusi mungkin bukan yang Anda inginkan.
Schmitzi

1

Unik, seperti yang diminta, (tetapi tidak diurutkan);
menggunakan lebih sedikit sumber daya sistem untuk kurang dari ~ 70 elemen (sebagaimana diuji dengan waktu);
ditulis untuk mengambil input dari stdin,
(atau memodifikasi dan memasukkan skrip lain):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"

0

Saya mendapatkan kiat yang lebih baik untuk mendapatkan entri non-duplikat dalam file

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.