Loại bỏ các dòng trùng lặp trong khi giữ thứ tự của các dòng


14
[root@server]# awk '!seen[$0]++' out.txt > cleaned
awk: (FILENAME=out.txt FNR=8547098) fatal error: internal error
Aborted
[root@server]#

"" Máy chủ "" có: 8 GByte RAM + 16 GByte SWAP, x> 300 GB không gian trống, amd64, CPU máy tính để bàn. Khoa học Linux 6.6. Không có gì khác chạy trên nó để tạo LOAD. Awk hủy bỏ sau vài giây .. out.txt là ~ 1.6 GByte. GNU Awk 3.1.7.

Câu hỏi : Làm thế nào tôi có thể loại bỏ các dòng trùng lặp trong khi vẫn giữ thứ tự của các dòng? Trường hợp cũng quan trọng, ví dụ: "A" và "a" là hai dòng khác nhau, phải giữ nó. Nhưng "a" và "a" là trùng lặp, chỉ cần cái đầu tiên.

Câu trả lời có thể là trong bất cứ điều gì .. nếu awk không tốt cho việc này .. thì perl / sed .. vấn đề có thể là gì?

[root@server]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61945
max locked memory       (kbytes, -l) 99999999
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 99999999
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61945
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[root@server]# 

Cập nhật: Tôi đã thử điều này trên máy RHEL, nó không hủy bỏ, nhưng tôi không có thời gian chờ đợi nó kết thúc .. tại sao SL linux lại khác với RHEL?

Cập nhật: Tôi đang thử một gues ảo Ubuntu 14 .. cho đến nay nó vẫn hoạt động! Đây không phải là vấn đề nan giải : mawk 1.3.3

root@asdf-VirtualBox:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 51331
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 51331
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
root@asdf-VirtualBox:~# 

2
Không có dòng trùng lặp trong ví dụ của bạn ...?
mikeerv

1
Là gì awkcác phiên bản trong hai máy?
cuonglm

bản cập nhật rrc cập nhật và bản cập nhật sl linux, không biết phiên bản rrc .. sl là: GNU Awk 3.1.7
somelooser28533

Lớn bao out.txtnhiêu Lệnh tương tự có hoạt động không nếu bạn thử nó trên một tệp nhỏ hơn? Có bao nhiêu người dùng trên máy? Đã có đủ bộ nhớ có sẵn cho quá trình? Có điều gì đặc biệt về dòng 8547098 của tệp đầu vào không?
terdon

Câu trả lời:


22

Tôi nghi ngờ nó sẽ tạo ra sự khác biệt, nhưng trong trường hợp, đây là cách làm điều tương tự trong Perl:

perl -ne 'print if ++$k{$_}==1' out.txt

Nếu vấn đề là giữ các dòng duy nhất trong bộ nhớ, điều đó sẽ có cùng vấn đề như awkbạn đã thử. Vì vậy, một cách tiếp cận khác có thể là:

cat -n out.txt | sort -k2 -k1n  | uniq -f1 | sort -nk1,1 | cut -f2-

Làm thế nào nó hoạt động:

  1. Trên hệ thống GNU, cat -nsẽ thêm số dòng cho mỗi dòng theo một số khoảng trắng và theo sau là ký tự <tab> . catống đại diện đầu vào này để sort.

  2. sort's -k2lựa chọn chỉ thị nó chỉ để xem xét các nhân vật từ trường thứ hai cho đến cuối dòng khi sắp xếp và sortchia ruộng theo mặc định trên trắng-không gian (hoặc catlà không gian chèn và <tab> ) .
    Khi được theo sau-k1n,sorthãy xem xét trường thứ 2 trước, và sau đó là thứ hai trong trường hợp các trường giống hệt nhau-k2, nó xem xét trường thứ nhất nhưng được sắp xếp theo số. Vì vậy, các dòng lặp lại sẽ được sắp xếp cùng nhau nhưng theo thứ tự chúng xuất hiện.

  3. Các kết quả được chuyển đến đường uniqphốwhwhich được yêu cầu bỏ qua trường đầu tiên ( -f1- và cũng được phân tách bằng khoảng trắng), và kết quả là một danh sách các dòng duy nhất trong tệp gốc và được đưa trở lại sort.
  4. Thời gian này sort sắp xếp trên trường đầu tiên ( catsố dòng được chèn) bằng số, lấy thứ tự sắp xếp trở lại như trong tệp gốc và đưa các kết quả này vàocut .
  5. Cuối cùng, cutloại bỏ các số dòng được chèn bởi cat. Điều này được thực hiện bằng cách cutchỉ in từ trường thứ 2 đến hết dòng (vàcut dấu phân cách mặc định là ký tự <tab> ) .

Để minh họa:

$ cat file
bb
aa
bb
dd
cc
dd
aa
bb
cc
$ cat -n file | sort -k2 | uniq -f1 | sort -k1 | cut -f2-
bb
aa    
dd
cc

Xin chào Terdon, OP cần giữ trật tự các dòng, vì vậy phương pháp cat | sort | uniq sẽ không hoạt động ... Giống như phiên bản perl của bạn mặc dù ...
Lambert

1
Giải pháp tốt đẹp với sort! Nhưng hầu hết sortcó thể tự làm uniqđể bạn có thể rút ngắn kịch bản của mình bằng cách sort -uk2 | sort -bk1,1n
Costas

@Costas có phải là nhất sort? Tôi nghĩ -ulà một tính năng GNU.
terdon

@don_crissti ah, vậy là xong, cảm ơn. Làm thế nào tôi có thể sử dụng nó ở đây mặc dù? Như tôi vừa nhận thấy (và chỉnh sửa để sửa chữa), tôi cần sắp xếp trên trường thứ 2 trước rồi sau đó vào số 1 để giữ thứ tự dòng. Làm thế nào tôi có thể sử dụng -uvà chỉ định rằng nó sẽ bỏ qua trường thứ 1? Theo đó man sort, -uđây không phải là một trong những lựa chọn khả thi -f, vì vậy tôi không nghĩ nó có thể được sử dụng ở đây.
terdon

1
đây là biến đổi Schwartzian ! (+1)
JJoao

7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

while(<>){ not $h{$_} and print and $h{$_}=1 }

CHỈNH SỬA 1: Nó thực sự hoạt động? (so sánh)

Sol1 : Terdon et all Schwartzian-transform-like one-liner
    cat -n _1 | sort -uk2 | sort -nk1 | cut -f2-

Sol2 : perl  + DB_File (this answer)
    perl dbfile-uniq _1

Sol3 : PO (John W. Gill solution has a similar behavior)
    awk '!seen[$0]++' _1

Sol4: Terdon perl
    perl -ne 'print if ++$k{$_}==1' _1

Trường hợp 1 : 100_000_000 số ngẫu nhiên (5 chữ số mỗi), 566Mbytes, 31_212 giá trị khác nhau:

$ while true ; do echo $RANDOM; done | head -100000000 > _1

Trường hợp 2 : 50_000_000 số rand (mỗi số có 10 chữ số), 516Mbyte, 48_351_464 giá trị khác nhau:

$ shuf _1 |  sed 'N;s/\n/ /' > _11

(các số sau không chính xác lắm):

┌────────┬────────┬────────────────┬────────┬──────┐
         Sol1    Sol2            Sol3    Sol4 
         sort...│ perl DB         awk     perl 
├────────┼────────┼────────────────┼────────┼──────┤
 case 1  6m15    6m17            0m28    0m28 
├────────┼────────┼────────────────┼────────┴──────┤
 case 2  11m15   81m44           out of memory 
├────────┼────────┼────────────────┼────────┬──────┤
 case 2          5m54 /cache=2G               
└────────┴────────┴────────────────┴────────┴──────┘

sol2 với bộ đệm là:

use DB_File;
use Fcntl ;

$DB_HASH->{'cachesize'} = 2000_000_000;
tie %h, 'DB_File', "_my.db", O_RDWR|O_CREAT|O_TRUNC, 0640, $DB_HASH;

while(<>){ not $h{$_} and print and $h{$_}=1 }

Sắp xếp cũng có thể được tối ưu hóa thêm một tùy chọn cachesize (không được thực hiện).

Một kết luận nhanh:

  • sort là một lệnh tuyệt vời!

1
sort -uk2sort -nk1,1là khác nhau. Cái đầu tiên xem xét từ khóa 2cd đến cuối dòng, cái thứ hai chỉ coi là khóa đầu tiên. Bạn nên thay đổi sort -nk1ở đó - nó thậm chí có thể nhanh hơn theo cách đó, nhưng nó chắc chắn sẽ đáng tin cậy hơn. Nhân tiện - đó là một số hộp đẹp.
mikeerv

@mikeerv, cảm ơn bạn đã bình luận. Vì K1,1 là duy nhất, sort -nk1 và sort -nk1,1 trả về một số kết quả. Tôi đã thử cả hai, kết quả là như nhau và thời gian không đặc biệt.
JJoao

Điều đó có ý nghĩa - mặc dù đã thử nó. Vì vậy, cat -nmột tab ? Tôi không biết lệnh đó hoạt động như thế nào.
mikeerv

1
@mikeerv, vui vẻ chuyển đổi cat -ntừng mục linetrong spaces + the number + \t + line- định dạng lý tưởng để sắp xếp và cắt
JJoao

1

Tôi đã sử dụng

awk -v BINMODE=rw '!($0 in a){a[$0];print}' infile >> outfile

BINMODE = rw: để giữ cho các đầu cuối dòng kết thúc hạnh phúc. (Tôi sống trong một môi trường os hỗn hợp)

Logic rất đơn giản.

Nếu dòng hiện tại không nằm trong mảng kết hợp thì hãy thêm nó vào mảng kết hợp và in ra đầu ra.

Có thể có những hạn chế về bộ nhớ với phương pháp này. Đối với các tệp và bộ tệp rất lớn, tôi đã sử dụng các biến thể về điều này, sử dụng lưu trữ tệp để vượt qua các giới hạn.


0

Các ngữ nghĩa bảo tồn trật tự của vấn đề của bạn có một tính chất tuyệt vời: bạn có thể chia nhỏ vấn đề. Bạn có thể làmsplit -l 1000000 trên tệp đầu vào; các dòng 1000000 mà nó tạo ra có các tên theo thứ tự từ vựng là tốt; sau đó uniqify các mảnh; và sau đó (như một lần chuyển thứ hai) xác nhận đầu ra của những cái đó.

Điều này giải quyết vấn đề hết bộ nhớ (bằng cách giới hạn yêu cầu bộ nhớ) với chi phí biến nó thành một giải pháp đa cấp.

Đặc biệt:

Tạo dữ liệu đầu vào:

$ cat make-uniqm-input.py
#!/usr/bin/env python
import random
n = 1000000
for i in xrange(0, n):
    print random.randint(1000, 2000)

$ python make-uniqm-input.py  > uniqm-input.txt

$ wc -l uniqm-input.txt
 1000000 uniqm-input.txt

Chia nhỏ dữ liệu đầu vào:

$ split -l 10000 uniqm-input.txt

$ ls x?? | head
xaa
xab
xac
xad
xae
xaf
xag
xah
xai
xaj

$ ls x?? | wc -l
     100

$ cat x?? | wc -l
 1000000

Chạy uniqifier tất cả cùng một lúc (giữ lại tất cả các dòng đầu vào duy nhất trong bộ nhớ):

# 'uniqm' is any order-preserving uniq implementation, such as
# gawk '!counts[$0]++'.
$ uniqm < uniqm-input.txt > output-no-splitting.txt

$ wc -l output-no-splitting.txt
    1001 output-no-splitting.txt

Chạy uniqifier trên các phần tách (chỉ giữ lại các dòng đầu vào duy nhất từ ​​mỗi phần trong bộ nhớ), sau đó giảm xuống như một lần chuyển thứ hai:

$ for x in x??; do uniqm < $x; done | uniqm > output-with-splitting.txt

$ wc -l output-with-splitting.txt
    1001 output-with-splitting.txt

Đối chiếu:

$ diff output-no-splitting.txt output-with-splitting.txt

$ head uniqm-input.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

$ head output-with-splitting.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Tôi không biết tỷ lệ giữa các dòng duy nhất và không duy nhất trong đầu vào của bạn, cũng như các dòng đầu vào được trộn lẫn như thế nào - vì vậy có một số điều chỉnh để thực hiện theo số lượng tệp phân tách bạn cần.


0

Một cách tiếp cận khác (đáng để đăng dưới dạng một câu trả lời riêng) là: thay vì cách tiếp cận tệp tách tạo tệp tạm thời, hãy thực hiện việc tạo khối trong chính phần mềm uniqifier. Ví dụ: sử dụng triển khai đơn vị Ruby cho mục đích giải thích:

require 'set'
line_batch_count = 50000 # tunable parameter
lines_seen = Set.new
line_number = 0
ARGF.each do |line|
   line_number += 1
   if (line_number % line_batch_count) == 0
     lines_seen.clear
   end
   unless lines_seen.include? line
      puts line
      lines_seen << line
   end
end

Ý tưởng là để xóa bộ băm thường xuyên. Sau đó, điều này trở thành lặp đi lặp lại:

$ cat uniqm-input.txt | ruby uniqm-capped.rb | wc -l
   20021

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | wc -l
    1001

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | head
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Vì vậy, bạn có thể chạy phiên bản giới hạn này nhiều lần, cho đến khi số dòng không thay đổi từ lần lặp này sang lần lặp tiếp theo.

Lưu ý rằng kỹ thuật capped-uniqm này không phụ thuộc vào ngôn ngữ: bạn có thể xóa lines_seenmảng mỗi N dòng cho dù bạn đang sử dụng awk, python, perl, C ++, v.v. Có các phương thức set-Clear cho tất cả các ngôn ngữ này; Tôi tin rằng awk's deletelà phi tiêu chuẩn nhưng phổ biến.

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.