Làm cách nào để xóa các dòng trùng lặp trong một tệp mà không sắp xếp nó trong Unix?


136

Có cách nào để xóa các dòng trùng lặp trong một tệp trong Unix không?

Tôi có thể làm điều đó với sort -uuniqcác lệnh, nhưng tôi muốn sử dụng sedhoặc awk. Điều đó có thể không?


11
nếu bạn có nghĩa là trùng lặp liên tiếp thì uniqmột mình là đủ.
Michael Krelin - tin tặc

và nếu không, tôi tin rằng nó có thể với awk, nhưng sẽ tiêu tốn khá nhiều tài nguyên trên các tệp lớn hơn.
Michael Krelin - tin tặc

Các bản sao stackoverflow.com/q/24324350stackoverflow.com/q/11532157 có câu trả lời thú vị lý tưởng nên được di chuyển ở đây.
tripleee

Câu trả lời:


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

seenlà một mảng kết hợp mà Awk sẽ chuyển mọi dòng của tệp tới. Nếu một dòng không nằm trong mảng thì seen[$0]sẽ đánh giá thành false. Đây !là một toán tử KHÔNG logic và sẽ đảo ngược sai thành đúng. Awk sẽ in các dòng mà biểu thức ước lượng là true. Số ++gia tăng seenđể seen[$0] == 1sau lần đầu tiên một dòng được tìm thấy và sau đó seen[$0] == 2, v.v.
Awk đánh giá mọi thứ nhưng 0""(chuỗi rỗng) là đúng. Nếu một dòng trùng lặp được đặt vào seenthì !seen[$0]sẽ đánh giá thành false và dòng đó sẽ không được ghi vào đầu ra.


5
Để lưu nó trong một tập tin, chúng ta có thể làm điều nàyawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal

5
Một cảnh báo quan trọng ở đây: nếu bạn cần thực hiện việc này cho nhiều tệp và bạn xử lý nhiều tệp hơn ở cuối lệnh hoặc sử dụng ký tự đại diện, mảng 'nhìn thấy' sẽ lấp đầy với các dòng trùng lặp từ TẤT CẢ các tệp. Thay vào đó, nếu bạn muốn xử lý từng tệp một cách độc lập, bạn sẽ cần phải làm một cái gì đó nhưfor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9

@ NickK9 mà việc sao chép tích lũy trên nhiều tệp là tuyệt vời. Mẹo hay
sfscs

31

Từ http://sed.sourceforge.net/sed1line.txt : (Xin đừng hỏi tôi cách thức hoạt động của nó ;-))

 # 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'

geekery ;-) +1, nhưng tiêu thụ tài nguyên là không thể tránh khỏi.
Michael Krelin - hacker

3
'$! N; / ^ (. *) \ n \ n / 1 / P; D 'có nghĩa là "Nếu bạn không ở dòng cuối cùng, hãy đọc ở dòng khác. Bây giờ hãy xem những gì bạn có và nếu nó không có nội dung tiếp theo là một dòng mới và sau đó cùng một nội dung, hãy in ra nội dung đó. các công cụ (lên đến dòng mới). "
Beta

2
'G; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'có nghĩa là, đại khái, "Nối toàn bộ không gian giữ dòng này, sau đó nếu bạn thấy một dòng trùng lặp, hãy ném toàn bộ thứ đó ra, nếu không thì sao chép lại toàn bộ mớ hỗn độn vào không gian giữ và in phần đầu tiên (đó là dòng bạn vừa đọc. "
Beta

$!phần cần thiết? Không sed 'N; /^\(.*\)\n\1$/!P; D'làm điều tương tự? Tôi không thể đưa ra một ví dụ trong đó hai cái khác nhau trên máy của tôi (fwiw tôi đã thử một dòng trống ở cuối với cả hai phiên bản và cả hai đều ổn).
eddi

1
Gần 7 năm sau và không ai trả lời @amichair ... <sniff> làm tôi buồn. ;) Dù sao, [ -~]đại diện cho một loạt các ký tự ASCII từ 0x20 (dấu cách) đến 0x7E (dấu ngã). Đây được coi là các ký tự ASCII có thể in (trang được liên kết cũng có 0x7F / xóa nhưng điều đó có vẻ không đúng). Điều đó làm cho giải pháp bị phá vỡ đối với bất kỳ ai không sử dụng ASCII hoặc bất kỳ ai sử dụng các ký tự tab .. Càng di động [^\n]bao gồm nhiều ký tự hơn ... thực tế tất cả đều ngoại trừ một ký tự.
Lớp B

14

Perl one-liner tương tự như giải pháp awk của @ jonas:

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

Biến thể này loại bỏ khoảng trắng theo dõi trước khi so sánh:

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

Biến thể này chỉnh sửa tệp tại chỗ:

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

Biến thể này chỉnh sửa tệp tại chỗ và tạo bản sao lưu file.bak

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

6

Một lớp lót mà Andre Miller đã đăng ở trên hoạt động ngoại trừ các phiên bản gần đây của sed khi tệp đầu vào kết thúc bằng một dòng trống và không có ký tự. Trên máy Mac, CPU của tôi chỉ quay.

Vòng lặp vô hạn nếu dòng cuối cùng trống và không có ký tự :

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

Không treo, nhưng bạn mất dòng cuối cùng

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

Lời giải thích nằm ở phần cuối của câu hỏi thường gặp về sed :

Người duy trì GNU sed cảm thấy rằng mặc dù có vấn đề về tính di động,
điều này sẽ gây ra, việc thay đổi lệnh N để in (thay vì
xóa) không gian mẫu phù hợp hơn với trực giác của một người
về cách một lệnh "nối dòng tiếp theo" phải hành xử.
Một thực tế khác có lợi cho sự thay đổi là "{N; lệnh;}" sẽ
xóa dòng cuối cùng nếu tệp có số dòng lẻ, nhưng
in dòng cuối nếu tệp có số dòng chẵn.

Để chuyển đổi các tập lệnh đã sử dụng hành vi trước đây của N (xóa
không gian mẫu khi đạt EOF) thành tập lệnh tương thích với
tất cả các phiên bản của sed, hãy thay đổi "N;" thành "$ d; N;" .


5

Một cách khác sử dụng Vim (tương thích Vi) :

Xóa các dòng trùng lặp, liên tiếp từ một tệp:

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

Xóa các dòng trùng lặp, không liên tục và không trống từ một tệp:

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


4

Giải pháp đầu tiên cũng là từ 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

ý tưởng cốt lõi là:

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

Giải thích:

  1. $!N;: nếu dòng hiện tại KHÔNG phải là dòng cuối cùng, hãy sử dụng Nlệnh để đọc dòng tiếp theo vàopattern space .
  2. /^(.*)\n\1$/!P: nếu nội dung của dòng điện cách nhau pattern spacehai , có nghĩa là dòng tiếp theo là dòng hiện tại, chúng ta KHÔNG thể in nó theo ý tưởng cốt lõi của chúng tôi; cách khác, có nghĩa là dòng hiện tại là sự xuất hiện LAST của tất cả các dòng liên tiếp trùng lặp của nó, bây giờ chúng ta có thể sử dụng lệnh để in các ký tự trong hiện tại util ( cũng in).duplicate string\nsamePpattern space\n\n
  3. D: Chúng tôi sử dụng Dlệnh để xóa các ký tự trong hiện tại pattern spaceutil \n( \ncũng xóa), sau đó nội dung củapattern space dòng tiếp theo.
  4. Dlệnh sẽ buộc sedphải nhảy tới FIRSTlệnh của nó $!N, nhưng KHÔNG đọc dòng tiếp theo từ tệp hoặc luồng đầu vào tiêu chuẩn.

Giải pháp thứ hai rất dễ hiểu (từ bản thân tôi):

$ 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

ý tưởng cốt lõi là:

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

Giải thích:

  1. đọc một dòng mới từ luồng đầu vào hoặc tệp và in nó một lần.
  2. sử dụng :looplệnh đặt một labeltên loop.
  3. sử dụng Nđể đọc dòng tiếp theo vào pattern space.
  4. sử dụng s/^(.*)\n\1$/\1/để xóa dòng hiện tại nếu dòng tiếp theo giống với dòng hiện tại, chúng tôi sử dụng slệnh để thực hiện deletehành động.
  5. nếu slệnh được thực thi thành công, sau đó sử dụng tlooplực lượng lệnh sedđể nhảy đến labeltên được đặt loop, sẽ thực hiện cùng một vòng lặp cho các dòng tiếp theo, không có các dòng liên tiếp trùng lặp của dòng đó latest printed; mặt khác, sử dụng Dlệnh cho deletedòng tương tự với latest-printed linevà buộc sedphải nhảy tới lệnh đầu tiên, đó là plệnh, nội dung của dòng điện pattern spacelà dòng mới tiếp theo.

cùng một lệnh trên Windows với 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

Điều này có thể đạt được bằng cách sử dụng awk
Dưới đây Line sẽ hiển thị các Giá trị duy nhất

awk file_name | uniq

Bạn có thể xuất các giá trị duy nhất này sang một tệp mới

awk file_name | uniq > uniq_file_name

tệp mới uniq_file_name sẽ chỉ chứa các giá trị duy nhất, không trùng lặp


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

Xóa các dòng trùng lặp bằng awk.


1
Điều này sẽ làm xáo trộn thứ tự của các dòng.
Vijay

1
Tệp văn bản 20 GB là gì? Quá chậm.
Alexander Lubyagin

Hơn bao giờ hết, những catlà vô ích. Dù sao, uniqđã tự làm điều này và không yêu cầu đầu vào phải chính xác một từ trên mỗi dòng.
tripleee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.