uniq một tập tin csv bỏ qua một cột, awk có thể?


7

Đưa ra tệp này (chú thích không phải là một phần của tệp, nhưng là một phần của giải thích) ...

x,a,001,b,c,d,y
x,a,002,b,c,e,yy
x,bb,003,b,d,e,y
x,c,004,b,d,e,y
x,c,005,b,d,e,y   # nb - dupe of row 4
x,dd,006,b,d,e,y
x,c,007,b,d,e,y   # nb - dupe of row 4 and 5
x,dd,008,b,d,f,y
x,dd,009,b,d,e,y   # nb - dupe of row 6
x,e,010,b,d,f,y

... Tôi muốn lấy đầu ra sau:

x,a,001,b,c,d,y
x,a,002,b,c,e,yy
x,bb,003,b,d,e,y
x,c,004,b,d,e,y
x,dd,006,b,d,e,y
x,dd,008,b,d,f,y
x,e,010,b,d,f,y

Nếu cột 3 bị cắt khỏi tệp và sau đó uniq được chạy trên tệp, thì nếu các hàng còn lại có cột ba giá trị được thêm lại vào đúng vị trí, thì tôi sẽ nhận được kết quả trên.

Nhưng tôi thực sự vật lộn, để đưa ra một cái gì đó sẽ làm điều này. Tôi hoan nghênh cơ hội tìm hiểu về các tiện ích xử lý văn bản của linux.

Hiệu suất: Các tệp không có khả năng tăng lên hơn 1 MB và chỉ có 1 tệp mỗi ngày.

Mục tiêu: Debian GNU / Linux 7 amd64, 256MB / Xeon.

Chỉnh sửa: ví dụ được điều chỉnh vì các trường không phải là băng thông cố định và một giải pháp liên quan uniq --skip-chars=nsẽ không hoạt động như tôi có thể nói.


Bạn đã đi đúng hướng tìm kiếm các tùy chọn để uniq- kiểm tra câu trả lời cập nhật của tôi. :)
peterph

Câu trả lời:


18

Với awk, bạn có thể làm:

awk -F, -vOFS=, '{l=$0; $3=""}; ! ($0 in seen) {print l; seen[$0]}'

2
wow, thanh lịch và đơn giản (và nhanh chóng, có lẽ, cũng vậy, bằng cách sử dụng tra cứu băm để so sánh với (các) dòng trước). Tuy nhiên, không phải nó cũng loại bỏ các bản sao xảy ra sau một cái gì đó ở giữa sao? . a, 999, b, c, d, y "sẽ không xuất hiện cùng với giải pháp của bạn nhưng (có lẽ) nên?)
Olivier Dulac

2
Bạn đúng rằng nó sẽ loại bỏ các dòng sau một cái gì đó ở giữa, và bạn đúng rằng uniq sẽ không làm điều đó. Nhưng nếu bạn nhìn vào OP, anh ấy dường như đã tin rằng uniq sẽ hành động theo cách mà kịch bản này làm, vì vậy kịch bản này có lẽ là điều anh ấy thực sự muốn.
Chiếc thìa ngon nhất

@TheSpooniest: tốt, sau đó chắc chắn +1 đến Stephane để đọc qua XYPro Hiệu ^^
Olivier Dulac

7

Cách đơn giản nhất :

sort -u -t, -k1,2 -k4
  • -u: chỉ xuất ra dòng đầu tiên bằng
  • -t,: sử dụng dấu phẩy làm dấu tách trường
  • -k1,2 -k4: chỉ sắp xếp trên các trường 1,2 và 4 và phần còn lại

Một tùy chọn khác là sắp xếp lại dữ liệu với sed(lưu ý tùy chọn GNU -r) ở cả hai bên - điều này đòi hỏi các bản ghi phải có độ dài cố định, nếu không nó sẽ thất bại (và hầu như không đáng chú ý):

sed -r       's/^([^,]+,[^,]+)(,[^,]+)(.*)$/\1\3\2/' \
    | sort \
    | uniq -w 12 \
    | sed -r 's/^([^,]+,[^,]+)(.*)(,[^,]+)$/\1\3\2/'

Bạn có thể muốn thêm một cái khác sortở cuối để sắp xếp nó theo số, nếu muốn (sử dụng -ktùy chọn để chọn theo những gì sắp xếp nên được thực hiện - tức là một cái gì đó như sed -k3 -t,)

Ví dụ, trong Perl, bạn có thể sử dụng các phần mà bạn muốn quyết định tính duy nhất là các khóa trong hàm băm (giá trị của các dòng đầy đủ) và chỉ chèn vào hàm băm nếu khóa chưa được xác định. Điều này tất nhiên sẽ linh hoạt hơn nhiều so với việc sử dụng sed(hoặc awk), nhưng cũng viết nhiều hơn (tôi ở xa Perl Guru, vì vậy rất có thể nó có thể được thực hiện theo cách thanh lịch hơn nhiều - xem các câu trả lời khác cho Perl giống như Giải pháp Perl):

#!/usr/bin/perl
use strict;

my %lines;
while (<>) {
    (my $k1, my $v, my $k2) = /^([^,]+,[^,]+,)([^,]+)(,.*)$/;
    my $k = $k1 . $k2;
    if (!exists($lines{$k})) {
        $lines{$k} = $_;
    }
}

for my $k (sort(keys(%lines))) {
    print $lines{$k};
}

Cảm ơn, không may các trường không có chiều rộng cố định. Tôi đã cập nhật câu hỏi, lời xin lỗi. Ví dụ của bạn không hoạt động trên hệ thống của tôi cho trường hợp kiểm tra cũ hoặc đã sửa đổi :(
jon

xóa câu trả lời của tôi và nâng cao câu trả lời của bạn - dường như thực hiện đại khái thuật toán tôi đã mô tả. tôi có lẽ muốn sử dụng chia chứ không phải regexp cho khai thác lĩnh vực, và nó sẽ đơn giản hơn rất nhiều với chỉ$lines{$k} = $_ unless $lines{$k};
cas

Rực rỡ, +1! Tôi đã cố gắng để làm điều đó với uniq's lựa chọn lĩnh vực và có thể không, đã không nghĩ rằng để sử dụng sort' s -u. Theo cách tôi nghĩ đó sort -ulà một phần mở rộng GNU, không phải POSIX, nhưng điều này sẽ hoạt động tốt trên các hệ thống Linux.
terdon

@terdon Tôi nghĩ bạn nói đúng rằng đó là một phần mở rộng.
peterph

Giải pháp đẹp và thanh lịch! ( perlmột) Tuy nhiên, với tư cách là một tân binh Perl, tôi cần một chút RTFM để hiểu những gì bạn đang làm ở đây. %lines(có thể dễ dàng nhận ra bằng dấu phần trăm của nó) là một mảng kết hợp (còn gọi là "biến băm" trong biệt ngữ Perl), có thể chấp nhận các chuỗi "thực" làm định danh khóa, không chỉ là số chỉ mục. Đây là yếu tố chịu trách nhiệm cho tất cả những "phép thuật" kỳ diệu được thực hiện ở đây.
cú pháp

3

Một cách để làm điều này với awk | sort | uniq | awk:

awk -F, '{a=$1;$1=$3;$3=a;print}' file | sort -k 2 | uniq -f 1 | awk -v OFS=',' '{a=$1;$1=$3;$3=a;print}'

2

Một cách đơn giản hơn Perl sẽ là:

perl -F"," -ane '$a=join(",",@F[0,1,3 .. $#F]); print unless $k{$a}; $k{$a}++' file

Các -atrường tách các @Fmảng thành mảng và -F","đặt dấu phân cách trường thành ,. -ncó nghĩa là chạy tập lệnh được cung cấp bởi -etrên mỗi dòng của tệp đầu vào.

Ý tưởng là lấy một lát mảng (các phần tử 0,1 và 3 cho đến hết mảng), nối chúng thành một chuỗi ( $a) và sử dụng chuỗi đó làm tham chiếu băm (mảng kết hợp). Sau đó, bạn chỉ in mỗi dòng nếu khóa băm chưa được nhìn thấy trước đó.


Điều đó sẽ nói rằng ab,c,1,da,bc,2,dlà như nhau. Bạn cần join(",". Ngoài ra, bạn có thể tối ưu hóa bằng cách di chuyển $k{$a}++vào unless() { }khối. Và sau đó, điều đó sẽ tương đương với awkgiải pháp của tôi ;-).
Stéphane Chazelas

Tôi không nghĩ rằng nó sẽ xác định ab,c,1,da,bc,2,dgiống hệt nhau - so sánh được thực hiện trên chuỗi được xây dựng lại (với dấu phẩy ở đúng nơi).
peterph

@peterph có nhưng đó là vì tôi đã sửa lỗi mà Stephane phát hiện và thêm vào join(",".
terdon

2
Chỉ là bạn không cần $k{$a}++nếu $ađã có %k. Bạn có thể làm cho nó ngắn hơn với:perl -F, -ane'print if!$k{join",",@F[0,1,3..-1]}++'
Stéphane Chazelas

1
Stephane, đề xuất cuối cùng của bạn không cung cấp đầu ra dự kiến, perdon của câu trả lời đã được chỉnh sửa.
bbaassssiiee
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.