Apakah ada cara untuk 'uniq' dengan kolom?


195

Saya memiliki file .csv seperti ini:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Saya harus menghapus duplikat email (seluruh baris) dari file (yaitu salah satu baris yang berisi overflow@example.comcontoh di atas). Bagaimana cara menggunakan uniqhanya bidang 1 (dipisahkan dengan koma)? Menurut man, uniqtidak memiliki opsi untuk kolom.

Saya mencoba sesuatu sort | uniqtetapi tidak berhasil.

Jawaban:


325
sort -u -t, -k1,1 file
  • -u untuk yang unik
  • -t, jadi koma adalah pembatas
  • -k1,1 untuk bidang kunci 1

Hasil tes:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
ini tidak berfungsi jika kolom berisi koma itu sendiri (dengan kutipan)
user775187

13
mengapa Anda membutuhkan, 1 in -k1,1? kenapa tidak hanya -k1?
hello_there_andy

18
@hello_there_andy: Ini dijelaskan dalam manual ( man sort). Itu singkatan dari posisi awal dan berhenti.
Serrano

3
@CarlSmotricz: Saya mengujinya dan mengkonfirmasi apa yang sortdikatakan halaman manual: " -u, --unique dengan -c, periksa untuk pemesanan yang ketat; tanpa -c, hasilkan hanya yang pertama dari proses yang sama ." Jadi, ini memang "kejadian duplikat pertama sebelum memilah."
Geremia

2
ini mengubah urutan garis juga, bukan?
rkachach

102
awk -F"," '!_[$1]++' file
  • -F mengatur pemisah bidang.
  • $1 adalah bidang pertama.
  • _[val]mencari valdi hash _(variabel biasa).
  • ++ kenaikan, dan mengembalikan nilai lama.
  • ! mengembalikan logika tidak.
  • ada cetakan tersirat di akhir.

4
Pendekatan ini dua kali lebih cepat daripada sort
bitek

9
Ini juga memiliki manfaat tambahan menjaga garis tetap dalam urutan aslinya!
AffluentOwl

8
Jika Anda memerlukan uniq terakhir alih-alih yang pertama maka skrip awk ini akan membantu:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima

3
@eshwar cukup tambahkan lebih banyak bidang ke indeks kamus! Misalnya, !_[$1][$2]++dapat digunakan untuk mengurutkan berdasarkan dua bidang pertama. awk-Fu saya tidak cukup kuat untuk dapat unik di berbagai bidang, meskipun. :(
Soham Chowdhury

1
Cemerlang! opsi ini lebih baik daripada jawabannya karena menjaga urutan baris
rkachach

16

Untuk mempertimbangkan beberapa kolom.

Sortir dan berikan daftar unik berdasarkan kolom 1 dan kolom 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : usus besar adalah pemisah
  • -k 1,1 -k 3,3 berdasarkan kolom 1 dan kolom 3

8

atau jika Anda ingin menggunakan uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

memberi:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Saya ingin menunjukkan kemungkinan penyederhanaan: Anda dapat membuang cat! Daripada mem-pip ke tr, biarkan tr membaca file menggunakan <. Menyalurkan melalui pipa catadalah komplikasi umum yang tidak perlu yang digunakan oleh pemula. Untuk sejumlah besar data ada efek kinerja yang bisa didapat.
Carl Smotricz

4
Senang mendengarnya. Terima kasih! (Tentu saja ini masuk akal, memikirkan "kucing" dan "kemalasan";))
Carsten C.

Pembalikan bidang dapat disederhanakan dengan rev.
Hielke Walinga

5

Jika Anda ingin mempertahankan duplikat terakhir yang bisa Anda gunakan

 tac a.csv | sort -u -t, -r -k1,1 |tac

Yang merupakan persyaratan saya

sini

tac akan membalikkan file baris demi baris


1

Ini cara yang sangat bagus.

Pertama memformat konten sedemikian rupa sehingga kolom yang akan dibandingkan untuk keunikan adalah lebar tetap. Salah satu cara untuk melakukan ini adalah menggunakan printf awk dengan specifier lebar bidang / kolom ("% 15s").

Sekarang opsi -f dan -w dari uniq dapat digunakan untuk melewati bidang / kolom sebelumnya dan untuk menentukan lebar perbandingan (kolom) lebar.

Inilah tiga contoh.

Dalam contoh pertama ...

1) Untuk sementara, buat kolom yang menarik menjadi lebar tetap lebih besar atau sama dengan lebar maks bidang.

2) Gunakan opsi -f uniq untuk melewati kolom sebelumnya, dan gunakan opsi -w uniq untuk membatasi lebar ke tmp_fixed_width.

3) Hapus spasi tambahan dari kolom untuk "mengembalikan" lebarnya (dengan asumsi tidak ada spasi tambahan sebelumnya).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

Dalam contoh kedua ...

Membuat kolom uniq baru 1. Kemudian hapus setelah filter uniq diterapkan.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Contoh ketiga sama dengan yang kedua, tetapi untuk banyak kolom.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

baik, lebih sederhana daripada mengisolasi kolom dengan awk, jika Anda perlu menghapus semuanya dengan nilai tertentu untuk file yang diberikan, mengapa tidak hanya melakukan grep -v:

misalnya untuk menghapus semuanya dengan nilai "col2" di baris tempat kedua: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Jika ini tidak cukup baik, karena beberapa baris mungkin dihilangkan secara tidak benar dengan kemungkinan nilai yang cocok ditampilkan di kolom yang berbeda, Anda dapat melakukan sesuatu seperti ini:

awk untuk mengisolasi kolom yang menyinggung: mis

awk -F, '{print $2 "|" $line}'

-F menetapkan bidang yang dibatasi untuk ",", $ 2 berarti kolom 2, diikuti oleh beberapa pembatas khusus dan kemudian seluruh baris. Anda kemudian dapat memfilter dengan menghapus garis yang dimulai dengan nilai yang menyinggung:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

dan kemudian menghapus barang-barang di depan pembatas:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(note - perintah sed ceroboh karena tidak termasuk nilai pelolosan. Juga pola sed harus benar-benar seperti "[^ |] +" (yaitu apa pun yang bukan pembatas). Tapi semoga ini cukup jelas.


3
Dia tidak ingin membersihkan garis, dia ingin mempertahankan satu salinan garis dengan string tertentu. Uniq adalah kasus penggunaan yang tepat.
ingyhere

-3

Dengan menyortir file dengan sortterlebih dahulu, Anda kemudian dapat mendaftar uniq.

Tampaknya mengurutkan file dengan baik:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Anda juga bisa melakukan beberapa sihir AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Ini tidak unik menurut kolom seperti yang ditanyakan dalam pertanyaan. Ini hanya unik untuk seluruh lini. Juga, Anda tidak perlu melakukan semacam untuk melakukan uniq. Keduanya saling eksklusif.
Javid Jamae

1
Ya kamu benar. Contoh terakhir melakukan apa yang ditanyakan, meskipun jawaban yang diterima jauh lebih bersih. Mengenai sort, maka uniq, sortperlu dilakukan sebelum melakukan uniqsebaliknya jika tidak bekerja (tetapi Anda dapat melewati perintah kedua dan hanya menggunakan sort -u). Dari uniq(1): "Saring baris pencocokan yang berdekatan dari INPUT (atau input standar), tulis ke OUTPUT (atau output standar)."
Mikael S

Ah, Anda benar tentang penyortiran sebelum uniq. Saya tidak pernah menyadari bahwa uniq hanya bekerja pada jalur yang berdekatan. Kurasa aku selalu menggunakan sort -u.
Javid Jamae
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.