Was ist ein guter Weg, um eine Textdatei zu filtern, um leere Zeilen zu entfernen?

11

Ich habe eine CSV-Datei (auf einem Mac), die eine Reihe von Leerzeilen enthält, z.

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"

Was ich konvertieren möchte:

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum  lorem ipsum ","2","3","4"

Ich weiß, dass es einen Einzeiler geben muss, aber ich weiß nicht, ob awk oder sed. Irgendwelche Tipps sehr geschätzt!

Pitosalas
quelle
1
Gemäß diesem Beispiel möchten Sie tatsächlich eingebettete Zeilenumbrüche aus den Feldern entfernen. Ist das korrekt? Mit anderen Worten, es gibt 6 Eingangsleitungen und sollten 2 Ausgangsleitungen sein?
Manatwork
Ja, genau das versuche ich loszuwerden: eingebettete Zeilenumbrüche in eine Zeichenfolge in Anführungszeichen.
Pitosalas
Was Sie also brauchen, ist etwas, das Zeilenumbrüche in Anführungszeichen entfernt. Das wird etwas komplizierter, da Sie einen mehrzeiligen regulären Ausdruck benötigen.
Tongpu

Antworten:

11

Sie können dazu den Grep- -vModus (Invert Match) verwenden:

grep -v '^$' old-file.csv > new-file.csv

Beachten Sie, dass dies unterschiedliche Dateien sein müssen, da Shell-Weiterleitungen funktionieren. Die Ausgabedatei wird geöffnet (und geleert), bevor die Eingabedatei gelesen wird. Wenn Sie mehr Utils haben (nicht standardmäßig unter Mac OS X), können Sie dies umgehen sponge:

grep -v '^$' file.csv | sponge file.csv

Aber dann fällt es Ihnen natürlich schwerer, zurück zu gehen, wenn etwas schief geht.

Wenn Sie "Leerzeilen" tatsächlich Leerzeichen enthalten können (es hört sich so an), können Sie dies stattdessen verwenden:

egrep -v '^[[:space:]]*$' old-file.csv > new-file.csv

Dadurch werden Leerzeilen sowie Zeilen, die nur Leerzeichen enthalten, ignoriert. Sie können natürlich die gleiche spongeTransformation durchführen.

derobert
quelle
Danke .... Keine leeren Zeilen gelöscht ... Vielleicht stimmt das ^ $ nicht überein? Aber die Zeilen sind nach meinem besten Wissen leer. Denken Sie daran, dies ist eine CD, die von Excel auf einem Mac erstellt wurde ... Sagt das etwas aus? (Lauf nicht schreiend weg, weil ich Excel gesagt habe :)
Pitosalas
@pitosalas Sie sind wahrscheinlich keine Leerzeilen. Versuchen Sie es zu ändern egrep -v '^[[:space:]]*$'... beachten Sie grep -> egrep und das seltsame neue Muster
derobert
Hat nicht funktioniert. Löschte ein paar doppelte Anführungszeichen und machte ein Chaos ...
Pitosalas
@pitosalas Ich bin mir nicht sicher, wie es doppelte Anführungszeichen löschen würde. Es sollte nur Leerzeichen löschen können. Und
genau
@pitosalas könnten Sie überprüfen, ob einer dieser Befehle etwas ausspuckt, das vernünftig aussieht (im Gegensatz zu Kauderwelsch): iconv -f utf16le file.csv | headodericonv -f utf16be file.csv | head
derobert
8

Die einfachste Option ist nur grep .. Hier bedeutet der Punkt "Alles abgleichen". Wenn die Zeile leer ist, stimmt sie nicht überein. Andernfalls wird die gesamte Zeile so gedruckt, wie sie ist.

Onturenio
quelle
6

So entfernen Sie leere Zeilen, an Ort und Stelle , mit ksh93:

sed '/./!d' file 1<>; file

Der <>;Umleitungsoperator ist spezifisch für ksh93 und entspricht dem Standardoperator <>, außer dass ksh die Datei nach Beendigung des Befehls abschneidet.

sed '/./!d'ist eine verschlungene Schreibweise grep ., aber leider beschwert sich GNU grep zumindest, wenn sein stdout auf dieselbe Datei verweist wie sein stdin. Man könnte sagen, man könnte schreiben:

grep . file | cat 1<>; file

Leider gibt es in ksh93 (zumindest in meiner Version (93u +)) einen Fehler, da die Datei in diesem Fall auf die Länge Null abgeschnitten zu sein scheint.

grep . file | { cat; } 1<>; file

Scheint diesen Fehler zu umgehen, aber jetzt ist er weitaus komplizierter als der Befehl sed.

Stéphane Chazelas
quelle
Bitte kombinieren Sie Ihre Antworten in einem gut formatierten Eintrag mit einer Kurzanleitung, wann jede Lösung eingesetzt werden sollte. Die unterschiedlichen Herangehensweisen an unterschiedliche Probleme, die alle in schwebenden Antworten zusammengefasst sind, haben diese Frage zu einer Katastrophe gemacht.
Caleb
@Caleb, es läuft alles darauf hinaus, dass die Frage sehr unklar ist, daher beziehen sich alle Antworten auf unterschiedliche Interpretationen der Frage. Für jede Antwort habe ich versucht zu sagen, welche Frage sie zu beantworten versucht.
Stéphane Chazelas
Nur zu Ihrer Information: Versucht, awk '/./' file 1<>; filewas funktioniert hat. Für mich ist das noch klarer alssed '/./!d'
grebneke
5

Hier ist ein PerlEinzeiler dafür:

perl -pi -e 's/^\s*\n//' yourfile

BEARBEITEN: Verbesserter Code basierend auf den Kommentaren von ruakh unten.

Joseph R.
quelle
1
Orperl -ni -e '/./ and print' yourfile
derobert
1
@peterph $ist ein Anker (dh eine Breite von Null), daher wird die neue Zeile ausgeschlossen. Was den überflüssigen Raum betrifft, so habe ich hinzugefügt, dass /xich nicht Perlversuchen wollte , "$" in den regulären Ausdruck zu interpolieren
Joseph R.
1
Sie brauchen das nicht $, vorausgesetzt, Sie haben das \n. (Alternativ - Sie brauchen das nicht , vorausgesetzt \n, Sie haben das \s*und das $; aber ich denke, es s/^\s*\n//macht klarer, dass die neue Zeile entfernt wird.) Sie brauchen das auch nicht /m; Dies hat keine Auswirkung auf diesen Befehl. Und wenn Sie den $und den Raum loswerden , brauchen Sie den nicht mehr /x.
Ruakh
1
@JosephR.: Das \nselbst kann entfernt werden; Was Sie nicht tun können, ist sowohl das $ als auch das zu entfernen \n. Hätte s/^\s*//also das Problem, das du beschreibst, s/^\s*$//wäre aber wegen dem \s*und dem in Ordnung $. (Sehen Sie, was ich meine?)
Ruakh
1
@JosephR.: Was passiert ist, $ kann vor einer neuen Zeile übereinstimmen (vorausgesetzt, dass entweder das /mFlag aktiviert ist oder die neue Zeile das allerletzte Zeichen der Zeichenfolge ist oder beides), aber es kann auch mit dem Ende der Zeichenfolge übereinstimmen. Zum Beispiel "abc" =~ m/^abc$/ist wahr. Im Fall von \s*$ist das \s*gierig genug, um die neue Zeile zu verschlingen, und dann $stimmt das mit dem Ende der Zeichenfolge überein. (Aber ich denke, es s/^\s*\n//ist sowieso klarer, so dass Ihre Antwort in Ordnung ist, wie es jetzt ist.)
Ruakh
5

Basierend auf der Klarstellung in den Kommentaren zu Ihrer Frage, etwas wie:

awk -v RS= -v ORS= 1

kann tun, was Sie wollen.

Ein leeres Datensatztrennzeichen ist ein Sonderfall, der besagt, awkdass Datensätze Absätze sein sollen (durch Leerzeichenfolgen getrennt). Wenn Sie auch das Trennzeichen für den Ausgabedatensatz auf die leere Zeichenfolge setzen, muss der Inhalt dieser Absätze (ohne Trennzeichen) verkettet werden. 1ist nur eine wahre Bedingung, um jeden Datensatz zu drucken.

Das würde jedoch die nachfolgende Newline weglassen, so dass Sie Folgendes tun könnten:

awk -v RS= -v ORS= '1;END{if (NR) printf "\n"}'
Stéphane Chazelas
quelle
3

Ich weiß, dass dies einfacher gewesen wäre, wenn ich die Datei gegeben hätte, aber leider enthielt sie vertrauliche Informationen, die ich nicht teilen konnte. In der Zwischenzeit schrieb ich mir ein Ruby-Skript, das den Trick zu tun schien:

require 'csv'
c = CSV.open("outfile1.csv", "w")
CSV.foreach("data.csv", :encoding => 'windows-1251:utf-8') do |row|
  row = row.map { |a| a.class == String ? a.gsub(/\r/, '') : a}
  c << row
end
c.close

Vielen Dank an alle für ihre Hilfe!

Pitosalas
quelle
2
awk '
    length == 0 {next} 
    /^[^"]/ && /"$/ {print; next} 
    {printf("%s", $0)}
' filename

produziert

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
Glenn Jackman
quelle
2

Ich habe eine Idee für eine mögliche Lösung für den Stackoverflow gefunden .

sed -i ':a;N;$!ba;s/[^"]\n\s*\n/ /g' file.csv

Sie sollten Ihre CSV-Datei wahrscheinlich sichern, bevor Sie sie testen, aber zumindest für das Beispiel, das Sie angegeben haben, funktioniert sie einwandfrei.

Eine gute Erklärung für das Innenleben dieses Ausdrucks finden Sie in der Antwort. Ich habe ihn nur bearbeitet, um nach Zeilen zu suchen, die nicht mit einem "( [^"]\n) enden .

Tongpu
quelle
1

Wenn Sie aus Ihrer eigenen Antwort Zeilenumbruchzeichen in Anführungszeichen entfernen möchten, haben Sie folgende Möglichkeiten:

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse'

Sie könnten auch die Verwendung von Perl verwenden -iFlagge zu bearbeiten die Dateien an Ort und Stelle .

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse' file1 file2...

Oder mit GNU awk:

 awk -v RS=\" 'NR%2==0 {gsub("\n","")}; {printf "%s", $0 RT}'

oder:

 awk -vRS=\" '1-NR%2{gsub("\n","")}{ORS=RT}1'

(wenn Sie um den kürzesten konkurrieren)

Beachten Sie, dass diejenigen annehmen , dass es kein entkommen doppelte Anführungszeichen in der Eingabe.

Stéphane Chazelas
quelle
0

Es sieht so aus, als ob Sie mehr wollen als nur leere Zeilen zu entfernen, sondern jede Folge von 2 oder mehr Zeilenumbrüchen entfernen.

Was du mit Perl machen könntest:

perl -0777 -pe 's/\n{2,}//gs' file

Sie könnten auch die Verwendung von Perl verwenden -iFlagge zu bearbeiten die Dateien an Ort und Stelle .

perl -0777 -pi -e 's/\n{2,}//gs' file1 file2...
Stéphane Chazelas
quelle
0

Es gibt eine immer kürzere Möglichkeit, leere Zeilen zu entfernen AWK:

awk 'NF' file

Aber um die gewünschte Ausgabe zu erhalten, ist lediglich ein einfacher Einzeiler erforderlich:

awk 'NF {printf("%s ", $0); i++;} !(i % 2) {printf("\n");}' file

Erläuterung

In AWKbedeutet eine leere Zeile, dass die Zeile / der Datensatz keine Felder enthält, NFdh die Variable (Anzahl der Felder) ist Null. Der eine Liner oben wird nur ausgeführt, wennNF > 0 alle Zeilen gedruckt werden, aber die leeren.

Das i++ ist der Zähler für nicht leere Zeilen.

Das !(i % 2)wird verwendet, um zwei aufeinanderfolgende nicht leere Zeilen auf die Weise Ihrer gewünschten Ausgabe zu drucken, dh jedes Mal, wenn ein Vielfaches von 2 gefunden wird, ergibt die moduloAnweisung !(i % 2)1, was die Verkettung von zwei nicht leeren Zeilen beendet.

Marcelo Augusto
quelle
Mein Fehler! Es tut uns leid. Ich habe nicht seine ganze Frage und die gewünschte Ausgabe gelesen. Die Antwort ist jetzt behoben. Vielen Dank. :-)
Marcelo Augusto
0

Sie können Vim im Ex-Modus verwenden:

ex -sc v/./d -cx b.csv
  1. v/./ finde leere Zeilen

  2. d löschen

  3. x speichern und schließen

Steven Penny
quelle