Hapus baris tajuk tambahan dari file, kecuali untuk baris pertama

18

Saya memiliki file yang terlihat seperti contoh mainan ini. File saya yang sebenarnya memiliki 4 juta baris, sekitar 10 di antaranya harus saya hapus.

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

Saya ingin menghapus garis yang terlihat seperti tajuk, kecuali untuk baris pertama.

File terakhir:

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

Bagaimana saya bisa melakukan ini?

Gayus Augustus
sumber

Jawaban:

26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. ambil baris tajuk dari file input ke dalam variabel
  2. cetak tajuk
  3. proses file dengan grepmenghilangkan baris yang cocok dengan header
  4. ambil output dari dua langkah di atas ke dalam file output
Jeff Schaller
sumber
2
atau mungkin{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar
Keduanya tambahan yang bagus. Terima kasih kepada don_crissti karena secara tidak langsung menunjukkan bahwa posix baru-baru ini menghapus sintaks -1 dari kepala, mendukung -n 1.
Jeff Schaller
3
@JeffSchaller, baru - baru ini 12 tahun lalu. Dan head -1telah usang selama beberapa dekade sebelum itu.
Stéphane Chazelas
36

Kamu bisa memakai

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

Ini akan menghapus baris dengan ID mulai dari baris 2.

bkmoney
sumber
3
bagus; atau untuk lebih spesifik dengan pencocokan pola, sed '2,${/^ID Data1 Data2$/d;}' file(menggunakan jumlah spasi yang tepat di antara kolom, tentu saja)
Jeff Schaller
Hm saya pikir Anda bisa menghilangkan titik koma hanya untuk 1 perintah, tapi ok.
bkmoney
Tidak dengan waras sed, tidak.
mikeserv
aaaand -i untuk kemenangan edit di tempat.
user2066657
4
Ataused '1!{/ID/d;}'
Stéphane Chazelas
10

Bagi yang tidak suka kurung keriting

sed -e '1n' -e '/^ID/d'
  • nberarti passNo. baris1
  • d hapus semua baris yang cocok yang dimulai dengan ^ID
Costas
sumber
5
Ini juga bisa disingkat menjadi sed '1n;/^ID/d'nama file. hanya sebuah saran
Valentin Bajrami
Perhatikan bahwa ini juga akan mencetak garis-garis seperti IDfooyang tidak sama dengan header (tidak mungkin membuat perbedaan dalam hal ini, tetapi Anda tidak pernah tahu).
terdon
6

Ini yang menyenangkan. Anda dapat menggunakan sedsecara langsung untuk menghapus semua salinan dari baris pertama dan meninggalkan semua yang lain di tempatnya (termasuk baris pertama itu sendiri).

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

1{h;n;}menempatkan baris pertama ke dalam ruang penahanan, mencetaknya, dan membaca di baris berikutnya — melompati sisa sedperintah untuk baris pertama. (Ini juga melewatkan 1tes pertama untuk baris kedua , tetapi itu tidak masalah karena tes itu tidak akan berlaku untuk baris kedua.)

G menambahkan baris baru diikuti dengan isi ruang pegang ke ruang pola.

/^\(.*\)\n\1$/dmenghapus isi dari ruang pola (sehingga melompati ke baris berikutnya) jika bagian setelah baris baru (yaitu apa yang ditambahkan dari ruang penahanan) persis cocok dengan bagian sebelum baris baru. Di sinilah baris yang menduplikasi header akan dihapus.

s/\n.*$//menghapus bagian teks yang ditambahkan oleh Gperintah, sehingga yang dicetak hanyalah baris teks dari file.

Namun, karena regex mahal, pendekatan yang sedikit lebih cepat adalah dengan menggunakan kondisi yang sama (dinegasikan) dan Pnaik ke baris baru jika bagian setelah baris baru (yaitu apa yang ditambahkan dari ruang penahanan) tidak persis cocok dengan porsi sebelum baris baru dan kemudian menghapus tanpa ruang pola:

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

Output saat diberikan input Anda adalah:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
Wildcard
sumber
@don_crissti, tambahan yang menarik; Terima kasih! Saya mungkin akan memilih yang lebih lama tapi setara sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; entah bagaimana lebih mudah bagi saya untuk membaca. :)
Wildcard
5

Berikut adalah beberapa pilihan lain yang tidak mengharuskan Anda mengetahui baris pertama sebelumnya:

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

The -nflag mengatakan perl loop atas file input-nya, menyimpan setiap baris sebagai $_. The $k=$_ if $.==1;menyimpan baris pertama ( $.adalah nomor baris, sehingga $.==1hanya akan berlaku untuk baris 1) sebagai $k. The print unless $k eq $_cetakan baris saat ini jika tidak sama dengan salah satu yang disimpan di $k.

Atau, hal yang sama di awk:

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

Di sini, kami menguji apakah baris saat ini sama dengan apa yang disimpan dalam variabel x. Jika pengujian $0!=xbernilai true (jika garis saat $0ini tidak sama dengan x), garis tersebut akan dicetak karena tindakan default untuk awk pada ekspresi benar adalah mencetak. Baris pertama ( NR==1) disimpan sebagai x. Karena ini dilakukan setelah memeriksa apakah baris saat ini cocok x, ini memastikan bahwa baris pertama juga akan dicetak.

terdon
sumber
Saya suka tidak harus tahu ide baris pertama karena membuatnya menjadi skrip umum untuk kotak peralatan Anda.
Mark Stewart
1
bahwa metode awk menciptakan entri array kosong / salah per baris yang berbeda; untuk baris 4M jika semuanya berbeda (tidak jelas dari Q) dan cukup pendek (tampak begitu) ini mungkin baik-baik saja, tetapi jika ada lebih banyak atau lebih baris ini bisa meronta-ronta atau mati. !($0 in a)menguji tanpa membuat dan menghindari ini, atau awk dapat melakukan logika yang sama seperti yang Anda miliki untuk perl: '$0!=x; NR==1{x=$0}'atau jika baris header dapat kosong'NR==1{x=$0;print} $0!=x'
dave_thompson_085
1
@ dave_thompson_085 di mana array per baris dibuat? Maksudmu !a[$0]? Mengapa itu membuat entri a?
terdon
1
Karena begitulah cara kerja awk; lihat gnu.org/software/gawk/manual/html_node/… terutama "CATATAN".
dave_thompson_085
1
@ dave_thompson_085 yah aku akan terkutuk! Terima kasih, saya tidak menyadarinya. Diperbaiki sekarang
terdon
4

AWK adalah alat yang cukup baik untuk tujuan seperti itu juga. Berikut contoh menjalankan kode:

$ 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

Hancurkan :

  • NR == 1 {print} memberitahu kita untuk mencetak baris pertama file teks
  • NR != 1 && $0!~/ID Data1 Data2/ operator logis &&memberi tahu AWK untuk mencetak garis yang tidak sama dengan 1 dan tidak mengandung ID Data1 Data2. Perhatikan kurangnya {print}bagian; di awk jika suatu kondisi pengujian dievaluasi ke true, diasumsikan untuk garis yang akan dicetak.
  • | head -n 10hanyalah tambahan kecil untuk membatasi output hanya 10 baris pertama. Tidak relevan dengan AWKbagian itu sendiri, hanya digunakan untuk tujuan demo.

Jika Anda menginginkannya dalam file, arahkan output perintah dengan menambahkan > newFile.txtdi akhir perintah, seperti:

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

Bagaimana itu bisa bertahan? Cukup bagus sebenarnya:

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

Catatan samping

File sampel yang dihasilkan dilakukan dengan pengulangan dari satu ke juta dan mencetak empat baris pertama file Anda (jadi 4 baris kali juta sama dengan 4 juta baris), yang memerlukan waktu 0,09 detik, omong-omong.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
Sergiy Kolodyazhnyy
sumber
Perhatikan bahwa ini juga akan mencetak garis-garis seperti ID Data1 Data2 fooyang tidak sama dengan header (tidak mungkin membuat perbedaan dalam hal ini, tetapi Anda tidak pernah tahu).
terdon
@terdon ya, tepat sekali. Namun OP hanya menetapkan satu pola yang ingin mereka hapus dan contohnya muncul untuk mendukung itu
Sergiy Kolodyazhnyy
3

Awk, beradaptasi dengan header apa pun secara otomatis:

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

yaitu, pada baris pertama, dapatkan tajuk dan cetak, dan baris berikutnya BERBEDA dari tajuk itu bisa dicetak.

FNR = Jumlah Rekaman dalam File saat ini, sehingga Anda dapat memiliki banyak file dan itu akan melakukan hal yang sama di masing-masing.

Olivier Dulac
sumber
2

Demi kelengkapan, solusi Perl IMO sedikit lebih elegan daripada @terdon memberi:

perl -i -p -e 's/^ID.*$//s if $. > 1' file
KWubbufetowicz
sumber
1
Ah, tapi inti saya adalah untuk menghindari perlunya menentukan pola dan bukannya membacanya dari baris pertama. Pendekatan Anda hanya akan menghapus baris yang dimulai dengan ID. Anda tidak memiliki jaminan bahwa ini tidak akan menghapus baris yang harus disimpan. Karena Anda memunculkan keanggunan, gtidak ada gunanya jika Anda menggunakan ^dan $. Bahkan, semua pilihan Anda untuk m///menjadi tidak berguna di sini kecuali s; mereka mengaktifkan fitur yang tidak Anda gunakan. Jadi $, s/^ID.*//sakan melakukan hal yang sama.
terdon
@terdon, cukup adil. Milikmu jauh lebih universal!
KWubbufetowicz
2

Hanya untuk mendorong kembali pada pertanyaan sedikit ... sepertinya input Anda sendiri adalah hasil dari catting beberapa file TSV. Jika Anda dapat membuat cadangan langkah dalam jalur pemrosesan Anda (jika Anda memiliki itu atau dapat berbicara dengan orang-orang yang melakukannya), Anda dapat menggunakan alat yang sadar header untuk menggabungkan data di tempat pertama, dan dengan demikian menghilangkan masalah keharusan untuk hapus baris tajuk tambahan.

Misalnya, menggunakan 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
John Kerl
sumber
1
Terima kasih telah menambahkan berita gembira ini. Ini akan sangat berguna di masa depan, karena sebagian besar jaringan pipa saya memerlukan penggabungan dan penggabungan file dari masing-masing sampel.
Gayus Augustus