Pertahankan hanya garis yang berisi jumlah pembatas yang tepat

9

Saya memiliki file csv besar dengan 10 bidang yang dipisahkan oleh koma. Sayangnya, beberapa baris salah format dan tidak mengandung tepat 10 koma (apa yang menyebabkan beberapa masalah ketika saya ingin membaca file menjadi R). Bagaimana saya bisa memfilter hanya garis yang berisi tepat 10 koma?

Miroslav Sabo
sumber
1
pertanyaan Anda dan pertanyaan yang ditautkan bukanlah pertanyaan yang sama. Anda bertanya bagaimana cara menangani garis dengan tidak lebih atau kurang dari jumlah kecocokan tertentu, sedangkan pertanyaan itu hanya membutuhkan jumlah kecocokan minimum. kenyataannya adalah bahwa pertanyaan itu lebih mudah dijawab - tidak memerlukan pemindaian garis secara penuh, atau (setidaknya, seperti yang sedada di sini) hanya sejauh satu kecocokan lebih dari yang dicari, meskipun pertanyaan ini. Anda seharusnya tidak menutup ini.
mikeserv
1
sebenarnya, melihat lebih dekat, penanya di sana tidak ingin lebih atau kurang dari pertandingan. pertanyaan itu membutuhkan judul baru. tetapi grepjawabannya tidak ada jawaban yang dapat diterima untuk kedua pertanyaan ...
mikeserv

Jawaban:

21

Satu lagi POSIX:

awk -F , 'NF == 11' <file

Jika garis memiliki 10 koma, maka akan ada 11 bidang di baris ini. Jadi kita cukup awkgunakan ,sebagai pembatas bidang. Jika jumlah bidang adalah 11, kondisinya NF == 11benar, awkkemudian lakukan tindakan default print $0.

cuonglm
sumber
5
Sebenarnya itu adalah hal pertama yang muncul di benak saya pada pertanyaan ini. Saya pikir itu berlebihan, tetapi melihat kode ... itu jelas lebih jelas. Untuk kepentingan orang lain: -Fset pemisah bidang dan NFmerujuk ke jumlah bidang dalam baris yang diberikan. Karena tidak ada blok kode {statement}yang ditambahkan ke kondisi NF == 11, tindakan standar adalah mencetak baris. (@cuonglm, jangan ragu untuk memasukkan penjelasan ini jika Anda suka.)
Wildcard
4
+1: Solusi yang sangat elegan dan mudah dibaca yang juga sangat umum. Saya dapat misalnya menemukan semua baris cacat denganawk -F , 'NF != 11' <file
Miroslav Sabo
@ardenhead: Sangat mudah untuk mendapatkannya, seperti yang Anda lihat OP katakan dalam komentarnya. Saya kadang-kadang menjawab dari ponsel saya, jadi sulit untuk menambahkan penjelasan detail.
cuonglm
1
@ mikeserv: Tidak, maaf jika saya membuat Anda bingung, itu hanya bahasa Inggris saya yang buruk. Anda tidak dapat memiliki 11 bidang dengan 1-9 koma.
cuonglm
1
@OlivierDulac: Ini melindungi Anda terhadap file mulai dengan -atau bernama -.
cuonglm
8

Menggunakan egrep(atau grep -Edalam POSIX):

egrep "^([^,]*,){10}[^,]*$" file.csv

Ini menyaring apa pun yang tidak mengandung 10 koma: cocok dengan garis penuh ( ^di awal dan $di akhir), yang mengandung tepat sepuluh pengulangan ( {10}) dari urutan "sejumlah karakter kecuali ',', diikuti oleh satu ','" ( ([^,]*,)), diikuti lagi oleh sejumlah karakter kecuali ',' ( [^,]*).

Anda juga dapat menggunakan -xparameter untuk menjatuhkan jangkar:

grep -xE "([^,]*,){10}[^,]*" file.csv

Ini kurang efisien daripada cuonglm 's awksolusi meskipun; yang terakhir biasanya enam kali lebih cepat pada sistem saya untuk jalur dengan sekitar 10 koma. Garis yang lebih panjang akan menyebabkan perlambatan besar.

Stephen Kitt
sumber
5

grepKode paling sederhana yang akan berfungsi:

grep -xE '([^,]*,){10}[^,]*'

Penjelasan:

-xmemastikan bahwa polanya harus cocok dengan seluruh garis, bukan hanya sebagian saja. Ini penting agar Anda tidak mencocokkan garis dengan lebih dari 10 koma.

-E berarti "regex diperpanjang", yang membuat lebih sedikit garis miring terbalik di regex Anda.

Tanda kurung digunakan untuk pengelompokan, dan {10}setelah itu berarti harus ada tepat sepuluh pertandingan dalam satu baris pola di dalam tanda kurung.

[^,]adalah kelas karakter — misalnya, [c-f]akan cocok dengan karakter tunggal apa pun yang a c, a d, a eatau a f, dan [^A-Z]akan cocok dengan karakter tunggal apa pun yang BUKAN huruf besar. Jadi [^,]cocok dengan setiap karakter kecuali koma.

The *setelah sarana kelas karakter "nol atau lebih dari ini."

Jadi bagian regex ([^,]*,)berarti "Karakter apa pun kecuali koma beberapa kali (termasuk nol kali), diikuti oleh koma" dan {10}menentukan 10 di antaranya. Kemudian [^,]*untuk mencocokkan sisa karakter non-koma ke akhir baris.

Wildcard
sumber
5
sed -ne's/,//11;t' -e's/,/&/10p' <in >out

Yang pertama bercabang garis apa pun dengan 11 atau lebih koma, dan kemudian mencetak yang tersisa hanya mereka yang cocok dengan 10 koma.

Rupanya saya menjawab ini sebelumnya ... Ini adalah saya-plagiarisme dari pertanyaan mencari persis 4 kejadian dari beberapa pola:

Anda dapat menargetkan [num]kemunculan pola dengan s///perintah ubstitusi sed dengan hanya menambahkan [num]ke perintah. Saat Anda tmencoba substitusi yang berhasil dan tidak menentukan :label target , test akan keluar dari skrip. Ini berarti yang harus Anda lakukan hanyalah menguji s///5atau lebih banyak koma, lalu mencetak yang tersisa.

Atau, setidaknya, yang menangani garis yang melebihi batas maksimum 4. Anda tampaknya juga memiliki persyaratan minimum. Untungnya, itu sesederhana itu:

sed -ne 's|,||5;t' -e 's||,|4p'

... hanya mengganti terjadinya-4 ,pada baris dengan dirinya sendiri dan taktik Anda petak on ke s///bendera ubstitution. Karena setiap baris yang cocok dengan ,5 atau lebih kali telah dipangkas, baris yang berisi 4 ,kecocokan hanya berisi 4.

mikeserv
sumber
1
@cuonglm - itulah yang sebenarnya saya miliki, pada awalnya, tetapi orang selalu mengatakan saya harus menulis kode yang lebih mudah dibaca. karena saya bisa membaca hal-hal yang orang lain perselisihkan sebagai tidak dapat dibaca, saya tidak yakin apa yang harus disimpan dan apa yang harus dijatuhkan ...? jadi saya menaruh koma kedua.
mikeserv
@cuonglm - Anda bisa mengejek saya - itu tidak akan menyakiti perasaan saya. saya bisa bercanda. jika kau mengejekku, itu sedikit lucu. tidak apa-apa - saya hanya tidak yakin dan ingin tahu. menurut saya, orang harus bisa menertawakan diri sendiri. Lagi pula, saya masih belum mengerti!
mikeserv
Haha, benar, ini pemikiran yang sangat positif. Ngomong-ngomong, sangat lucu mengobrol dengan Anda dan kadang-kadang, Anda membuat otak saya stres .
cuonglm
Sangat menarik bahwa dalam jawaban ini , jika saya ganti s/hello/world/2dengan s//world/2, GNU sed berfungsi dengan baik. Dengan dua seddari pusaka, /usr/5bin/posix/sedangkat segfault, /usr/5bin/sedmasuk ke loop infinitif.
cuonglm
@mikeserv, mengacu pada diskusi kami sebelumnya tentang seddanawk (dalam komentar) —Saya menyukai jawaban ini dan memutakhirkannya, tetapi perhatikan terjemahan dari awkjawaban yang diterima adalah: "Cetak baris dengan 11 bidang" dan terjemahan dari sedjawaban ini adalah: " Cobalah untuk menghapus koma ke-11; lompati ke baris berikutnya jika Anda gagal. Cobalah untuk mengganti koma ke-10 dengan dirinya; cetak baris jika Anda berhasil. " The awkjawabannya memberikan petunjuk untuk komputer seperti yang Anda akan mengungkapkannya dalam bahasa Inggris. ( awkbaik untuk data berbasis lapangan.)
Wildcard
4

Membuang beberapa pendek python:

#!/usr/bin/env python2
with open('file.csv') as f:
    print '\n'.join(line for line in f if line.count(',') == 10)

Ini akan membaca setiap baris dan memeriksa apakah jumlah koma di baris sama dengan 10 line.count(',') == 10, jika demikian cetak maka akan menjadi baris.

heemayl
sumber
2

Dan inilah cara Perl:

perl -F, -ane 'print if $#F==10'

The -nmenyebabkan perluntuk membaca file baris input dengan baris dan mengeksekusi script yang diberikan oleh -epada setiap baris. The -abergantian pada membelah otomatis: setiap baris masukan akan dibagi pada nilai yang diberikan oleh -F(di sini, koma) dan disimpan sebagai array @F.

The $#F(atau, lebih umum $#array), adalah indeks tertinggi dari array @F. Sejak array mulai 0, garis dengan 11 bidang akan memiliki @Fdari 10. Script, oleh karena itu, mencetak baris jika memiliki 11 bidang.

terdon
sumber
Anda juga bisa melakukan print if @F==11sebagai array dalam konteks skalar mengembalikan jumlah elemen.
Sobrique
1

Jika bidang dapat berisi koma atau baris baru, kode Anda perlu memahami csv. Contoh (dengan tiga kolom):

$ cat filter.csv
a,b,c
d,"e,f",g
1,2,3,4
one,two,"three
...continued"

$ cat filter.csv | python3 -c 'import sys, csv
> csv.writer(sys.stdout).writerows(
> row for row in csv.reader(sys.stdin) if len(row) == 3)
> '
a,b,c
d,"e,f",g
one,two,"three
...continued"

Saya kira sebagian besar solusi sejauh ini akan membuang baris kedua dan keempat.

Peter Otten
sumber