Behalten Sie nur die Zeilen bei, die die genaue Anzahl der Trennzeichen enthalten

9

Ich habe eine riesige CSV-Datei mit 10 durch Kommas getrennten Feldern. Leider sind einige Zeilen fehlerhaft und enthalten nicht genau 10 Kommas (was einige Probleme verursacht, wenn ich die Datei in R einlesen möchte). Wie kann ich nur die Zeilen herausfiltern, die genau 10 Kommas enthalten?

Miroslav Sabo
quelle
1
Ihre Frage und die verknüpfte Frage sind nicht dieselbe Frage. Sie fragen, wie mit Zeilen umgegangen werden soll, die nicht mehr oder weniger als eine bestimmte Anzahl von Übereinstimmungen enthalten, während für diese Frage nur eine minimale Anzahl von Übereinstimmungen erforderlich ist. Die Realität ist, dass die Frage leichter beantwortet werden kann - es ist nicht erforderlich, eine Zeile vollständig zu scannen oder (zumindest wie sedhier) nur bis zu einer Übereinstimmung mehr, als gesucht wird, obwohl diese Frage dies tut. Sie sollten dies nicht geschlossen haben.
Mikeserv
1
eigentlich näheres Hinsehen, die Fragesteller es nicht will , nicht mehr oder weniger als Streichhölzer. Diese Frage braucht einen neuen Titel. aber die grepAntwort gibt es keine akzeptable Antwort für beide Fragen ...
Mikeserv

Antworten:

21

Ein weiterer POSIX:

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

Wenn die Zeile 10 Kommas enthält, enthält diese Zeile 11 Felder. So einfach machen wir awkGebrauch ,als Feldtrennzeichen. Wenn die Anzahl der Felder 11, die Bedingung NF == 11wahr ist , awkdann die Standardaktion ausführt print $0.

cuonglm
quelle
5
Das ist eigentlich das erste, was mir bei dieser Frage in den Sinn kam. Ich dachte, es wäre übertrieben, aber wenn ich mir den Code ansehe, ist es sicher klarer. Zum Nutzen anderer: -FLegt das Feldtrennzeichen fest und NFbezieht sich auf die Anzahl der Felder in einer bestimmten Zeile. Da {statement}an die Bedingung kein Codeblock angehängt wird NF == 11, wird standardmäßig die Zeile gedruckt. (@cuonglm, zögern Sie nicht, diese Erklärung aufzunehmen, wenn Sie möchten.)
Wildcard
4
+1: Sehr elegante und lesbare Lösung, die auch sehr allgemein ist. Ich kann zB alle fehlerhaften Linien mitawk -F , 'NF != 11' <file
Miroslav Sabo
@gardenhead: Es ist einfach, es zu bekommen, wie Sie sehen, sagte der OP in seinem Kommentar. Ich antworte manchmal von meinem Handy aus, daher ist es schwierig, die Details zu erklären.
Cuonglm
1
@mikeserv: Nein, tut mir leid, wenn ich dich verwirrt habe, es ist nur mein schlechtes Englisch. Sie können nicht 11 Felder mit 1-9 Kommas haben.
Cuonglm
1
@OlivierDulac: Es schützt Sie vor Dateistart mit -oder benannt -.
Cuonglm
8

Verwenden egrep(oder grep -Ein POSIX):

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

Dies filtert alles heraus, was keine 10 Kommas enthält: Es entspricht den vollständigen Zeilen ( ^am Anfang und $am Ende) und enthält genau zehn Wiederholungen ( {10}) der Sequenz "beliebig viele Zeichen außer ',', gefolgt von einem einzelnen ','". ( ([^,]*,)), gefolgt von einer beliebigen Anzahl von Zeichen außer ',' ( [^,]*).

Sie können den -xParameter auch verwenden , um die Anker zu löschen:

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

Dies ist jedoch weniger effizient als die Lösung von cuonglmawk . Letzteres ist auf meinem System für Zeilen mit etwa 10 Kommas normalerweise sechsmal schneller. Längere Leitungen führen zu enormen Verlangsamungen.

Stephen Kitt
quelle
5

Der einfachste grepCode, der funktioniert:

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

Erläuterung:

-xstellt sicher, dass das Muster mit der gesamten Linie übereinstimmt und nicht nur mit einem Teil davon. Dies ist wichtig, damit Sie keine Zeilen mit mehr als 10 Kommas abgleichen.

-E bedeutet "erweiterter regulärer Ausdruck", wodurch weniger Rückschläge in Ihrem regulären Ausdruck entstehen.

Klammern werden zum Gruppieren verwendet, und das {10}bedeutet, dass in einer Reihe des Musters in den Klammern genau zehn Übereinstimmungen vorhanden sein müssen.

[^,]ist eine Zeichenklasse - [c-f]würde beispielsweise mit jedem einzelnen Zeichen übereinstimmen, das a c, a d, an eoder an ist f, und [^A-Z]würde mit jedem einzelnen Zeichen übereinstimmen, das KEIN Großbuchstabe ist. Entspricht also [^,]jedem einzelnen Zeichen außer einem Komma.

Das *Nach der Zeichenklasse bedeutet "null oder mehr davon".

Der Regex-Teil ([^,]*,)bedeutet also "Beliebiges Zeichen außer einem Komma beliebig oft (einschließlich Null), gefolgt von einem Komma" und {10}gibt 10 davon an. Dann [^,]*, um den Rest der Nicht-Komma-Zeichen mit dem Ende der Zeile abzugleichen.

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

Das verzweigt zuerst jede Zeile mit 11 oder mehr Kommas und druckt dann nur diejenigen aus, die mit 10 Kommas übereinstimmen.

Anscheinend habe ich das schon einmal beantwortet ... Hier ist ein Ich-Plagiat aus einer Frage, die nach genau 4 Vorkommen eines Musters sucht:

Sie können das [num]Auftreten eines Musters mit einem sed s///ubstitution-Befehl anvisieren, indem Sie einfach das [num]zum Befehl hinzufügen . Wenn Sie teine erfolgreiche Ersetzung anstreben und keine Zielbezeichnung angeben :, tverzweigt sich est aus dem Skript. Dies bedeutet, dass Sie nur auf s///5oder mehrere Kommas testen und dann drucken müssen, was noch übrig ist.

Oder zumindest die Zeilen, die Ihr Maximum von 4 überschreiten. Anscheinend haben Sie auch eine Mindestanforderung. Zum Glück ist das genauso einfach:

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

... ersetzen Sie einfach das 4. Vorkommen ,einer Zeile durch sich selbst und heften Sie Ihren Rint pan die s///Ubstitutionsflaggen. Da alle Zeilen, die ,5 oder mehr Mal übereinstimmen , bereits beschnitten wurden, enthalten die Zeilen mit 4 ,Übereinstimmungen nur 4.

mikeserv
quelle
1
@cuonglm - das hatte ich eigentlich zuerst, aber die Leute sagen mir immer, ich sollte besser lesbaren Code schreiben. da ich die Sachen lesen kann, die andere als unlesbar bestreiten, bin ich mir nicht sicher, was ich behalten und was ich fallen lassen soll ...? Also habe ich das zweite Komma gesetzt.
Mikeserv
@cuonglm - du kannst mich verspotten - es wird meine Gefühle nicht verletzen. Ich kann einen Witz machen. Wenn du mich verspottest, war es ein bisschen lustig. Es ist in Ordnung - ich war mir einfach nicht sicher und wollte es wissen. Meiner Meinung nach sollten die Leute in der Lage sein, über sich selbst zu lachen. Jedenfalls verstehe ich es immer noch nicht!
Mikeserv
Haha, richtig, es ist ein sehr positives Denken. Wie auch immer, es ist sehr lustig, mit dir zu plaudern und manchmal belastest du mein Gehirn.
Cuonglm
Es ist interessant , dass in dieser Antwort , wenn ich ersetzen s/hello/world/2mit s//world/2, GNU funktioniert sed. Mit zwei sedaus dem Erbstück, /usr/5bin/posix/sedSegfault erhöhen, /usr/5bin/sedgeht in Endlosschleife.
Cuonglm
@mikeserv, in Bezug auf unsere frühere Diskussion über sedundawk (in Kommentaren) - Ich mag diese Antwort und habe sie positiv bewertet, aber beachten Sie, dass die Übersetzung der akzeptierten awkAntwort lautet: "Zeilen mit 11 Feldern drucken " und die Übersetzung dieser sedAntwort lautet: " Versuchen Sie, das 11. Komma zu entfernen. Fahren Sie mit der nächsten Zeile fort, wenn Sie fehlschlagen. Versuchen Sie, das 10. Komma durch sich selbst zu ersetzen. Drucken Sie die Zeile, wenn Sie erfolgreich sind. " Die awkAntwort gibt dem Computer die Anweisungen so, wie Sie sie auf Englisch ausdrücken würden. ( awkist gut für feldbasierte Daten.)
Wildcard
4

Wirf ein paar kurze python:

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

Dadurch wird jede Zeile gelesen und geprüft, ob die Anzahl der Kommas in der Zeile gleich 10 line.count(',') == 10ist. Wenn dies der Fall ist, wird die Zeile gedruckt.

heemayl
quelle
2

Und hier ist ein Perl-Weg:

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

Die -nUrsachen perlfür das zeilenweise Lesen der Eingabedatei und das Ausführen des -ein jeder Zeile angegebenen Skripts . Die -aautomatische Aufteilung wird aktiviert: Jede Eingabezeile wird nach dem durch -F(hier ein Komma) angegebenen Wert aufgeteilt und als Array gespeichert @F.

Der $#F(oder allgemeiner $#array) ist der höchste Index des Arrays @F. Da Arrays bei beginnen 0, hat eine Zeile mit 11 Feldern ein @Fvon 10. Das Skript druckt daher die Zeile, wenn es genau 11 Felder enthält.

terdon
quelle
Sie können auch print if @F==11als Array in einem skalaren Kontext die Anzahl der Elemente zurückgeben.
Sobrique
1

Wenn Felder Kommas oder Zeilenumbrüche enthalten können, muss Ihr Code csv verstehen . Beispiel (mit drei Spalten):

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

Ich nehme an, dass die meisten Lösungen bisher die zweite und vierte Zeile verwerfen würden.

Peter Otten
quelle