Xóa các dòng tiêu đề bổ sung khỏi tệp, ngoại trừ dòng đầu tiên


18

Tôi có một tập tin trông giống như ví dụ đồ chơi này. Tập tin thực tế của tôi có 4 triệu dòng, khoảng 10 trong số đó tôi cần xóa.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Tôi muốn xóa các dòng trông giống như tiêu đề, ngoại trừ dòng đầu tiên.

Tập tin cuối cùng:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

Tôi có thể làm cái này như thế nào?

Câu trả lời:


26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. lấy dòng tiêu đề từ tệp đầu vào thành một biến
  2. in tiêu đề
  3. xử lý tệp grepđể bỏ qua các dòng khớp với tiêu đề
  4. nắm bắt đầu ra từ hai bước trên vào tập tin đầu ra

2
hoặc có lẽ{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar

Cả hai bổ sung tốt. Cảm ơn don_crissti đã gián tiếp chỉ ra rằng posix gần đây đã loại bỏ cú pháp -1 khỏi đầu, ủng hộ -n 1.
Jeff Schaller

3
@JeffSchaller, gần đây như 12 năm trước. Và head -1đã bị lỗi thời trong nhiều thập kỷ trước đó.
Stéphane Chazelas

36

Bạn có thể dùng

sed '2,${/ID/d;}'

Điều này sẽ xóa các dòng có ID bắt đầu từ dòng 2.


3
tốt đẹp; hoặc cụ thể hơn với việc khớp mẫu, sed '2,${/^ID Data1 Data2$/d;}' file(dĩ nhiên sử dụng đúng số lượng khoảng cách giữa các cột)
Jeff Schaller

Tôi nghĩ bạn có thể bỏ dấu chấm phẩy chỉ với 1 lệnh, nhưng ok.
bkmoney

Không w / sane seds, không.
mikeerv

aaaand -i cho chiến thắng chỉnh sửa tại chỗ.
dùng2066657

4
Hoặcsed '1!{/ID/d;}'
Stéphane Chazelas

10

Đối với những người không thích dấu ngoặc nhọn

sed -e '1n' -e '/^ID/d'
  • ncó nghĩa là passdòng số1
  • d xóa tất cả (các) dòng trùng khớp bắt đầu bằng ^ID

5
Điều này cũng có thể được rút ngắn thành sed '1n;/^ID/d'tên tệp. chỉ là một gợi ý
Valentin Bajrami

Lưu ý rằng điều này cũng sẽ in các dòng IDfookhông giống với tiêu đề (không thể tạo ra sự khác biệt trong trường hợp này, nhưng bạn không bao giờ biết).
terdon

6

Đây là một niềm vui. Bạn có thể sử dụng sedtrực tiếp để loại bỏ tất cả các bản sao của dòng đầu tiên và để mọi thứ khác vào vị trí (bao gồm cả dòng đầu tiên).

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}đặt dòng đầu tiên vào không gian giữ, in nó và đọc trong dòng tiếp theo, bỏ qua các sedlệnh còn lại cho dòng đầu tiên. (Nó cũng bỏ qua 1thử nghiệm đầu tiên cho dòng thứ hai , nhưng điều đó không quan trọng vì thử nghiệm đó sẽ không áp dụng cho dòng thứ hai.)

G nối thêm một dòng mới theo sau là nội dung của không gian giữ vào không gian mẫu.

/^\(.*\)\n\1$/dxóa nội dung của không gian mẫu (do đó bỏ qua dòng tiếp theo) nếu phần sau dòng mới (nghĩa là phần được nối từ không gian giữ) khớp chính xác với phần trước dòng mới. Đây là nơi các dòng trùng lặp tiêu đề sẽ bị xóa.

s/\n.*$//xóa phần văn bản đã được thêm bởi Glệnh, để những gì được in chỉ là dòng văn bản từ tệp.

Tuy nhiên, vì regex rất tốn kém, nên cách tiếp cận nhanh hơn một chút là sử dụng cùng một điều kiện (phủ định) và Pchuyển sang dòng mới nếu phần sau dòng mới (nghĩa là phần được thêm vào từ không gian giữ) không khớp chính xác với phần trước dòng mới và sau đó xóa vô điều kiện không gian mẫu:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

Đầu ra khi cho đầu vào của bạn là:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200


@don_crissti, bổ sung thú vị; cảm ơn! Tôi có thể sẽ chọn lâu hơn nhưng tương đương sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; bằng cách nào đó nó dễ dàng hơn cho tôi để đọc. :)
tự đại diện


5

Dưới đây là một vài lựa chọn khác không yêu cầu bạn phải biết trước dòng đầu tiên:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

Các -nlá cờ nói với perl để lặp qua tập tin đầu vào của nó, tiết kiệm mỗi dòng như $_. Lưu $k=$_ if $.==1;dòng đầu tiên ( $.là số dòng, vì vậy $.==1sẽ chỉ đúng với dòng thứ 1) là $k. Dòng print unless $k eq $_in dòng hiện tại nếu nó không giống với dòng được lưu trong $k.

Ngoài ra, điều tương tự trong awk:

awk '$0!=x;(NR==1){x=$0}' file 

Ở đây, chúng tôi kiểm tra xem dòng hiện tại có giống với dòng được lưu trong biến không x. Nếu kiểm tra $0!=xđánh giá là đúng (nếu dòng hiện tại $0không giống như x), dòng sẽ được in vì hành động mặc định cho awk trên biểu thức đúng là in. Dòng đầu tiên ( NR==1) được lưu dưới dạng x. Vì việc này được thực hiện sau khi kiểm tra xem dòng hiện tại có khớp hay không x, điều này đảm bảo rằng dòng đầu tiên cũng sẽ được in.


Tôi muốn không phải biết ý tưởng dòng đầu tiên vì nó biến nó thành một kịch bản tổng quát cho hộp công cụ của bạn.
Mark Stewart

1
phương thức awk đó tạo ra một mục nhập mảng trống / sai trên mỗi dòng riêng biệt; đối với các dòng 4M nếu tất cả các dòng khác nhau (không rõ ràng từ Q) và khá ngắn (có vẻ như vậy) thì điều này có thể ổn, nhưng nếu có nhiều dòng hoặc nhiều hơn thì dòng này có thể bị đập hoặc chết. !($0 in a)kiểm tra mà không tạo và tránh điều này, hoặc awk có thể thực hiện logic giống như bạn có đối với perl: '$0!=x; NR==1{x=$0}'hoặc nếu dòng tiêu đề có thể trống'NR==1{x=$0;print} $0!=x'
dave_thedom_085

1
@ dave_thndry_085 một mảng trên mỗi dòng được tạo ở đâu? Ý bạn là !a[$0]sao? Tại sao điều đó sẽ tạo ra một mục trong a?
terdon

1
Bởi vì đó là cách thức hoạt động của awk; xem gnu.org/software/gawk/manual/html_node/ Khăn đặc biệt là "LƯU Ý".
dave_thndry_085

1
@ dave_thndry_085 tôi cũng sẽ bị nguyền rủa! Cảm ơn, tôi đã không nhận thức được điều đó. Đã sửa bây giờ.
terdon

4

AWK là một công cụ khá tốt cho mục đích như vậy là tốt. Đây là mẫu mã chạy:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Phá vỡ :

  • NR == 1 {print} bảo chúng tôi in dòng đầu tiên của tệp văn bản
  • NR != 1 && $0!~/ID Data1 Data2/ Toán tử logic &&yêu cầu AWK in dòng không bằng 1 và không chứa ID Data1 Data2. Lưu ý thiếu {print}một phần; trong awk nếu một điều kiện kiểm tra được đánh giá là đúng, nó được giả sử cho dòng được in.
  • | head -n 10chỉ là một bổ sung nhỏ để giới hạn đầu ra chỉ 10 dòng đầu tiên. Không liên quan đến AWKchính bộ phận, chỉ được sử dụng cho mục đích demo.

Nếu bạn muốn điều đó trong một tệp, hãy chuyển hướng đầu ra của lệnh bằng cách nối thêm > newFile.txtvào cuối lệnh, như vậy:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

Làm thế nào để nó giữ lên? Thực sự khá tốt:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Lưu ý bên

Tệp mẫu đã tạo được thực hiện với vòng lặp từ một đến triệu và in bốn dòng đầu tiên của tệp của bạn (vì vậy 4 dòng nhân triệu bằng 4 triệu dòng), nhân tiện, mất 0,09 giây.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt

Lưu ý rằng điều này cũng sẽ in các dòng ID Data1 Data2 fookhông giống với tiêu đề (không thể tạo ra sự khác biệt trong trường hợp này, nhưng bạn không bao giờ biết).
terdon

@terdon vâng, chính xác. Tuy nhiên, OP chỉ chỉ định một mẫu mà họ muốn xóa và ví dụ của anh ta xuất hiện để hỗ trợ điều đó
Sergiy Kolodyazhnyy

3

Awk, tự động thích ứng với bất kỳ tiêu đề nào:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

tức là, trên dòng đầu tiên, hãy lấy tiêu đề và in nó, và dòng tiếp theo KHÁC từ tiêu đề đó được in.

FNR = Số lượng bản ghi trong tệp hiện tại, để bạn có thể có nhiều tệp và nó sẽ làm tương tự trong mỗi tệp.


2

Để hoàn thiện, giải pháp Perl IMO thanh lịch hơn một chút so với @terdon đã đưa ra:

perl -i -p -e 's/^ID.*$//s if $. > 1' file

1
Ah, nhưng toàn bộ quan điểm của tôi là để tránh sự cần thiết phải chỉ định mẫu và thay vào đó đọc nó từ dòng đầu tiên. Cách tiếp cận của bạn sẽ chỉ cần xóa bất kỳ dòng bắt đầu với ID. Bạn không đảm bảo rằng điều này sẽ không xóa các dòng nên được giữ. Vì bạn đã mang đến sự thanh lịch, glà vô nghĩa nếu bạn sử dụng ^$. Trong thực tế, tất cả các tùy chọn của bạn m///là vô dụng ở đây ngoại trừ s; họ kích hoạt các tính năng bạn không sử dụng. Vì vậy $, s/^ID.*//ssẽ làm điều tương tự.
terdon

@terdon, đủ công bằng. Của bạn là phổ quát hơn nhiều!
KWubbufetowicz

2

Chỉ cần đẩy lùi câu hỏi một chút ... có vẻ như có thể đầu vào của bạn là kết quả của việc kết hợp nhiều tệp TSV lại với nhau. Nếu bạn có thể sao lưu một bước trong quy trình xử lý của mình (nếu bạn sở hữu điều đó hoặc có thể nói chuyện với những người thực hiện), bạn có thể sử dụng công cụ nhận biết tiêu đề để nối dữ liệu ngay từ đầu và do đó loại bỏ vấn đề phải loại bỏ các dòng tiêu đề bổ sung.

Ví dụ: sử dụng Miller :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200

1
Cảm ơn bạn đã thêm miếng ngon này. Điều này sẽ cực kỳ hữu ích trong tương lai, vì hầu hết các đường ống của tôi yêu cầu nối và hợp nhất các tệp từ các mẫu riêng lẻ.
Gaius Augustus
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.