Bagaimana cara menghapus baris duplikat dalam file tanpa mengurutkannya di Unix?

136

Apakah ada cara untuk menghapus garis duplikat dalam file di Unix?

Saya dapat melakukannya dengan sort -udan uniqperintah, tetapi saya ingin menggunakan sedatau awk. Apakah itu mungkin?

Vijay
sumber
11
jika yang Anda maksud adalah duplikat berurutan maka itu uniqsaja sudah cukup.
Michael Krelin - hacker
dan jika tidak, saya percaya itu mungkin dengan awk, tetapi akan memakan banyak sumber daya pada file yang lebih besar.
Michael Krelin - hacker
Duplikat stackoverflow.com/q/24324350 dan stackoverflow.com/q/11532157 memiliki jawaban menarik yang idealnya harus dimigrasikan di sini.
tripleee

Jawaban:

290
awk '!seen[$0]++' file.txt

seenadalah asosiatif-array yang Awk akan melewati setiap baris file. Jika suatu baris tidak ada dalam array maka seen[$0]akan bernilai false. Ini !adalah operator TIDAK logis dan akan membalikkan false ke true. Awk akan mencetak garis di mana ekspresi bernilai true. The ++bertahap seensehingga seen[$0] == 1setelah pertama kali garis ditemukan dan kemudian seen[$0] == 2, dan sebagainya.
Awk mengevaluasi segalanya kecuali 0dan ""(string kosong) menjadi true. Jika garis duplikat ditempatkan di seenkemudian !seen[$0]akan mengevaluasi ke false dan garis tidak akan ditulis ke output.

Jonas Elfström
sumber
5
Untuk menyimpannya dalam file, kita bisa melakukan iniawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal
5
Peringatan penting di sini: jika Anda perlu melakukan ini untuk banyak file, dan Anda menangani lebih banyak file di akhir perintah, atau menggunakan wildcard ... larik 'terlihat' akan diisi dengan garis duplikat dari SEMUA file. Jika Anda ingin memperlakukan setiap file secara independen, Anda harus melakukan sesuatu sepertifor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9
@ NickK9 bahwa de-duping secara kumulatif di banyak file itu sendiri mengagumkan. Tip bagus
sfscs
31

Dari http://sed.sourceforge.net/sed1line.txt : (Tolong jangan tanya saya bagaimana ini bekerja ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
Andre Miller
sumber
geekery ;-) +1, tetapi konsumsi sumber daya tidak dapat dihindari.
Michael Krelin - hacker
3
'$! N; /^(.*)1n1/1/!P; D 'berarti "Jika Anda tidak berada di baris terakhir, baca di baris lain. Sekarang lihat apa yang Anda miliki dan jika itu TIDAK diikuti oleh baris baru dan kemudian hal yang sama lagi, cetak barang-barang itu. Sekarang hapus barang (hingga baris baru). "
Beta
2
'G; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'berarti, secara kasar, "Tambahkan seluruh ruang pegang baris ini, maka jika Anda melihat garis duplikat membuang semuanya, jika tidak, salin seluruh kekacauan kembali ke ruang pegang dan cetak bagian pertama (yang merupakan baris yang baru saja Anda buat baca. "
Beta
Apakah $!bagian itu perlu? Tidak sed 'N; /^\(.*\)\n\1$/!P; D'melakukan hal yang sama? Saya tidak dapat memberikan contoh di mana keduanya berbeda pada mesin saya (fwiw saya memang mencoba baris kosong di akhir dengan kedua versi dan keduanya baik-baik saja).
eddi
1
Hampir 7 tahun kemudian dan tidak ada yang menjawab @amichair ... <sniff> membuat saya sedih. ;) Bagaimanapun, [ -~]mewakili berbagai karakter ASCII dari 0x20 (spasi) hingga 0x7E (tilde). Ini dianggap sebagai karakter ASCII yang dapat dicetak (halaman yang ditautkan juga memiliki 0x7F / hapus tetapi sepertinya tidak benar). Itu membuat solusi rusak bagi siapa pun yang tidak menggunakan ASCII atau siapa pun yang menggunakan, katakanlah, karakter tab .. Semakin portabel [^\n]mencakup lebih banyak karakter ... semua dari mereka kecuali satu, pada kenyataannya.
B Layer
14

Perl one-liner mirip dengan solusi awk @ jonas:

perl -ne 'print if ! $x{$_}++' file

Variasi ini menghilangkan spasi spasi sebelum membandingkan:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

Variasi ini mengedit file di tempat:

perl -i -ne 'print if ! $x{$_}++' file

Variasi ini mengedit file di tempat, dan membuat cadangan file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file
Chris Koknat
sumber
6

Satu-liner yang diposting Andre Miller di atas berfungsi kecuali untuk versi sed terbaru ketika file input berakhir dengan baris kosong dan tanpa karakter. Di Mac saya, CPU saya hanya berputar.

Infinite loop jika baris terakhir kosong dan tidak memiliki karakter :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Tidak menggantung, tetapi Anda kehilangan baris terakhir

sed '$d;N; /^\(.*\)\n\1$/!P; D'

Penjelasannya ada di bagian paling akhir dari FAQ sed :

Pemelihara GNU merasa bahwa terlepas dari masalah portabilitas
ini akan menyebabkan, mengubah perintah N untuk mencetak (daripada
menghapus) ruang pola lebih konsisten dengan intuisi seseorang
tentang bagaimana perintah untuk "menambahkan baris berikutnya" harus berperilaku.
Fakta lain yang mendukung perubahan adalah bahwa "{N; command;}" akan
menghapus baris terakhir jika file memiliki jumlah ganjil baris, tetapi
mencetak baris terakhir jika file memiliki jumlah garis genap.

Untuk mengonversi skrip yang menggunakan perilaku N sebelumnya (menghapus
ruang pola setelah mencapai EOF) menjadi skrip yang kompatibel dengan
semua versi sed, ubah "N;" ke "$ d; N;" .

Bradley Kreider
sumber
5

Cara alternatif menggunakan Vim (kompatibel Vi) :

Hapus duplikat, baris berturut-turut dari file:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Hapus duplikat, baris tidak berturut-turut dan kosong dari file:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

Bohr
sumber
4

Solusi pertama juga dari http://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

ide intinya adalah:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Menjelaskan:

  1. $!N;: jika baris saat ini BUKAN baris terakhir, gunakan Nperintah untuk membaca baris selanjutnyapattern space .
  2. /^(.*)\n\1$/!P: jika konten saat ini pattern spaceadalah dua duplicate stringdipisahkan oleh \n, yang berarti baris berikutnya adalah samedengan baris saat ini, kami TIDAK dapat mencetaknya sesuai dengan ide inti kami; jika tidak, yang berarti baris saat ini adalah penampilan TERAKHIR dari semua baris duplikat berturut-turut, sekarang kita dapat menggunakanP perintah untuk mencetak karakter dalam pattern spaceutil saat ini \n(\n juga dicetak).
  3. D: kami menggunakan Dperintah untuk menghapus karakter saat inipattern space util\n ( \njuga dihapus), kemudian kontenpattern space adalah baris berikutnya.
  4. dan Dperintah akan memaksased untuk melompat ke FIRSTperintahnya $!N, tetapi TIDAK membaca baris berikutnya dari file atau aliran input standar.

Solusi kedua mudah dipahami (dari diri saya sendiri):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

ide intinya adalah:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Menjelaskan:

  1. baca baris baru dari aliran input atau file dan cetak sekali.
  2. gunakan :loopperintah atur labelnamaloop .
  3. gunakan Nuntuk membaca baris berikutnya ke dalampattern space .
  4. gunakan s/^(.*)\n\1$/\1/untuk menghapus baris saat ini jika baris berikutnya sama dengan baris saat ini, kami menggunakan sperintah untuk melakukandelete tindakan.
  5. jika sperintah dieksekusi dengan sukses, maka gunakan tloopkekuatan perintah seduntuk melompat ke labelnama loop, yang akan melakukan loop yang sama ke baris berikutnya menggunakan tidak ada duplikat baris berturut-turut dari garis yang latest printed; jika tidak, gunakan Dperintah ke deletebaris yang sama dengan latest-printed line, dan paksakan seduntuk melompat ke perintah pertama, yang merupakan pperintah, konten saat ini pattern spaceadalah baris baru berikutnya.
Weike
sumber
perintah yang sama pada Windows dengan busybox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
scavenger
-1

Ini dapat dicapai menggunakan awk
Below Line akan menampilkan Nilai unik

awk file_name | uniq

Anda dapat menampilkan nilai unik ini ke file baru

awk file_name | uniq > uniq_file_name

file baru uniq_file_name hanya akan berisi nilai unik, tidak ada duplikat

Aashutosh
sumber
-4
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

Menghapus garis duplikat menggunakan awk.

Sadhun
sumber
1
Ini akan mengganggu urutan garis.
Vijay
1
Berapa file teks sekitar 20 GB? Terlalu lambat.
Alexander Lubyagin
Seperti biasa, yang cattidak berguna. Lagi pula, uniqsudah melakukan ini dengan sendirinya, dan tidak memerlukan input tepat satu kata per baris.
tripleee