Mantieni solo le righe contenenti il ​​numero esatto di delimitatori


9

Ho un enorme file CSV con 10 campi separati da virgole. Sfortunatamente, alcune righe sono malformate e non contengono esattamente 10 virgole (cosa causa alcuni problemi quando voglio leggere il file in R). Come posso filtrare solo le righe che contengono esattamente 10 virgole?


1
la tua domanda e la domanda collegata non sono la stessa domanda. ti chiedi come gestire le linee con un numero non superiore o inferiore a un certo numero di corrispondenze, mentre quella domanda richiede solo un conteggio minimo delle corrispondenze. la realtà è che la domanda ha una risposta più facile - non richiede la scansione di una riga per intero, o (almeno, come sedfa qui) solo per quanto riguarda una corrispondenza in più di quanto si cerchi, anche se questa domanda lo fa. Non avresti dovuto chiudere questo.
Mikeserv,

1
in realtà, guardando più da vicino, il richiedente non vuole più o meno delle partite. quella domanda ha bisogno di un nuovo titolo. ma la greprisposta non è una risposta accettabile per nessuna delle due domande ...
mikeserv

Risposte:


21

Un altro POSIX:

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

Se la riga ha 10 virgole, ci saranno 11 campi in questa riga. Così abbiamo semplicemente fare awkuso ,come delimitatore di campo. Se il numero di campi è 11, la condizione NF == 11è vera, awkquindi esegue l'azione predefinita print $0.


5
Questa è in realtà la prima cosa che mi è venuta in mente su questa domanda. Ho pensato che fosse eccessivo, ma guardando il codice ... è sicuramente più chiaro. A beneficio di altri: -Fimposta il separatore di campo e si NFriferisce al numero di campi in una determinata riga. Poiché nessun blocco di codice {statement}viene aggiunto alla condizione NF == 11, l'azione predefinita è stampare la riga. (@cuonglm, sentiti libero di incorporare questa spiegazione se vuoi.)
Wildcard

4
+1: soluzione molto elegante e leggibile che è anche molto generale. Ad esempio, posso trovare tutte le righe malformate conawk -F , 'NF != 11' <file
Miroslav Sabo,

@gardenhead: è facile ottenerlo, come vedi l'OP nel suo commento. A volte rispondo dal mio cellulare, quindi è difficile aggiungere la spiegazione dei dettagli.
cuonglm,

1
@mikeserv: No, scusa se ti ho fatto confondere, è solo il mio cattivo inglese. Non puoi avere 11 campi con 1-9 virgole.
cuonglm,

1
@OlivierDulac: ti protegge dall'avvio del file -o dal nome -.
data

8

Utilizzo egrep(o grep -Ein POSIX):

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

Questo filtra tutto ciò che non contiene 10 virgole: corrisponde a righe complete ( ^all'inizio e $alla fine), contenente esattamente dieci ripetizioni ( {10}) della sequenza "qualsiasi numero di caratteri tranne ',', seguito da un singolo ','" ( ([^,]*,)), seguito da un numero qualsiasi di caratteri tranne ',' ( [^,]*).

È inoltre possibile utilizzare il -xparametro per eliminare le ancore:

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

Questo è meno efficiente di cuonglm 's awksoluzione però; quest'ultimo è in genere sei volte più veloce sul mio sistema per le linee con circa 10 virgole. Le linee più lunghe causeranno enormi rallentamenti.


5

Il grepcodice più semplice che funzionerà:

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

Spiegazione:

-xassicura che il motivo deve corrispondere all'intera linea, anziché solo a una parte di essa. Questo è importante quindi non abbinare le righe con più di 10 virgole.

-E significa "regex esteso", il che rende meno backslash-escape nel tuo regex.

Le parentesi vengono utilizzate per il raggruppamento e, {10}successivamente, significa che devono esserci esattamente dieci corrispondenze in una riga del modello all'interno delle parentesi.

[^,]è una classe di caratteri: ad esempio, [c-f]corrisponderebbe a qualsiasi singolo carattere che è un c, a d, an eo an fe [^A-Z]corrisponderebbe a qualsiasi singolo carattere che NON sia una lettera maiuscola. Quindi [^,]corrisponde a qualsiasi singolo carattere tranne una virgola.

Il *dopo la classe di caratteri significa "zero o più di questi".

Quindi la parte regex ([^,]*,)significa "Qualsiasi carattere tranne una virgola un numero qualsiasi di volte (incluso zero volte), seguito da una virgola" e ne {10}specifica 10. Quindi [^,]*abbinare il resto dei caratteri non virgola alla fine della riga.


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

Che prima si ramifica su qualsiasi riga con 11 o più virgole, quindi stampa ciò che rimane solo quelle che corrispondono a 10 virgole.

Apparentemente ho risposto a questa prima ... Ecco un me-plagio da una domanda che cerca esattamente 4 occorrenze di qualche schema:

È possibile indirizzare la [num]ricorrenza di un modello con un s///comando sed ubstitution semplicemente aggiungendo [num]il comando. Quando si trichiede una sostituzione corretta e non si specifica :un'etichetta di destinazione , l' test si dirama fuori dallo script. Ciò significa che tutto ciò che devi fare è testare s///5o più virgole, quindi stampare ciò che rimane.

O, almeno, che gestisce le linee che superano il tuo massimo di 4. Apparentemente hai anche un requisito minimo. Fortunatamente, è altrettanto semplice:

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

... basta sostituire la 4a occorrenza di ,su una linea con se stessa e apporre pla tua s///benda sulle bandiere ubstitution. Poiché qualsiasi riga corrispondente a ,5 o più volte è già stata potata, le righe contenenti 4 ,corrispondenze ne contengono solo 4.


1
@cuonglm - questo è quello che avevo effettivamente, all'inizio, ma la gente mi dice sempre che dovrei scrivere un codice più leggibile. dal momento che posso leggere le cose che gli altri contestano come illeggibili, non sono sicuro di cosa tenere e cosa lasciare ...? così ho messo la seconda virgola.
Mikeserv,

@cuonglm - puoi deridermi - non ferirà i miei sentimenti. posso fare uno scherzo. se mi prendevi in ​​giro, era un po 'divertente. va bene - non ne ero sicuro e volevo saperlo. secondo me, le persone dovrebbero essere in grado di ridere di se stesse. comunque, ancora non capisco!
Mikeserv,

Haha, giusto, è un pensiero molto positivo. Ad ogni modo, è molto divertente chattare con te e, a volte, mi stressi il cervello.
cuonglm,

E 'interessante che in questa risposta , se sostituisco s/hello/world/2con s//world/2, GNU sed funzionano bene. Con due seddal cimelio, /usr/5bin/posix/sedalzare segfault, /usr/5bin/sedva in loop infinito.
cuonglm,

@mikeserv, in riferimento alla nostra precedente discussione su sedeawk (nei commenti) —Mi piace questa risposta e l'ho votata, ma noto che la traduzione della awkrisposta accettata è: "Stampa righe con 11 campi" e la traduzione di questa sedrisposta è: " Tenta di rimuovere l'undicesima virgola; passa alla riga successiva in caso di errore. Tenta di sostituire la decima virgola con se stessa; stampa la riga se riesci. " La awkrisposta fornisce le istruzioni al computer nel modo in cui le esprimeresti in inglese. ( awkè buono per i dati basati sul campo.)
Wildcard

4

Lanciando un po 'corto python:

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

Questo leggerà ogni riga e verificherà se il numero di virgole nella riga è uguale a 10 line.count(',') == 10, in tal caso stampa la riga.


2

Ed ecco un modo Perl:

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

Le -ncause perlleggono il suo file di input riga per riga ed eseguono lo script fornito da -eogni riga. Le -aspire di splitting automatico: ciascuna linea di ingresso vengono divisi sul valore dato da -F(qui, una virgola) e salvato come matrice @F.

Il $#F(o, più in generale $#array), è l'indice più alto dell'array @F. Poiché le matrici iniziano da 0, una riga con 11 campi avrà un @Fdi 10. Lo script, quindi, stampa la riga se ha esattamente 11 campi.


Si potrebbe anche fare print if @F==11come un array in un contesto scalare restituisce il numero di elementi.
Sobrique,

1

Se i campi possono contenere virgole o nuove righe, il codice deve comprendere CSV. Esempio (con tre colonne):

$ 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"

Suppongo che la maggior parte delle soluzioni finora eliminerebbe la seconda e la quarta fila.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.