Entfernen Sie das Komma zwischen den Anführungszeichen nur in einer durch Kommas getrennten Datei

23

Ich habe eine Eingabedatei, die durch Kommas ( ,) getrennt ist. In Anführungszeichen eingeschlossene Felder enthalten ein Komma. Hier ist die Beispielzeile

123,"ABC, DEV 23",345,534.202,NAME

Ich muss alle Kommas entfernen, die in den doppelten Anführungszeichen und den doppelten Anführungszeichen vorkommen. Daher sollte die obige Zeile wie unten gezeigt analysiert werden

123,ABC DEV 23,345,534.202,NAME

Ich habe Folgendes versucht, sedaber nicht die erwarteten Ergebnisse erzielt.

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

Jeder schneller Trick mit sed, awkoder jedes anderes Unix - Dienstprogramm bitte?

mtk
quelle
Ich bin nicht sicher, was Sie versuchen, aber das Dienstprogramm "csvtool" ist weitaus besser zum Parsen von csv als generische Tools wie sed oder awk. Es ist in fast jeder Linux-Distribution.
Figtrap

Antworten:

32

Wenn die Anführungszeichen ausgeglichen sind, möchten Sie die Kommas zwischen den anderen Anführungszeichen entfernen. Dies kann folgendermaßen ausgedrückt werden awk:

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

Ausgabe:

123,ABC DEV 23,345,534.202,NAME

Erläuterung

Das -F"Kommando awk trennt die Zeile an den doppelten Anführungszeichen, was bedeutet, dass jedes zweite Feld der Text zwischen den Anführungszeichen ist. Die for-Schleife gsub, kurz für global substitute, wird in jedem anderen Feld ausgeführt und ersetzt comma ( ",") durch nothing ( ""). Der 1am Ende ruft den Standard - Code-Block: { print $0 }.

Thor
quelle
1
Können Sie gsubkurz erläutern, wie dieser eine Liner funktioniert? Bitte.
MTK
Vielen Dank! Dieses Skript funktioniert wirklich gut, aber können Sie die einsame 1 am Ende des Skripts erklären? -} 1 '-
CocoaEv
@ CocoaEv: Es wird ausgeführt { print $0 }. Das habe ich auch zur Erklärung hinzugefügt.
Thor
2
Dieser Ansatz hat ein Problem: manchmal die csv hat Zeilen , die mehrere Linien, wie umspannen: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix innerhalb eines mehrzeiligen doppelt zitieren mehrere Linien und verschachtelt „“ überall: der gesamte (dh "...."Teil versetzte werden soll , und im Innern ,sollte ersetzt / entfernt ...): In diesem Fall werden in Ihrem Skript keine doppelten Anführungszeichen angezeigt, und die Lösung ist nicht einfach. doppelte Anführungszeichen ... + \"
Olivier Dulac
1
Liebte diese Lösung, aber ich habe sie optimiert, da ich oft die Kommas behalten, aber immer noch eingrenzen möchte. Stattdessen habe ich die Kommas außerhalb der Anführungszeichen in Pipes geändert und die csv in eine psv-Datei konvertiert:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega
7

Es gibt eine gute Antwort, wenn sed einfach einmal mit einer Schleife verwendet wird :

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

Erläuterung:

  • :a; ist ein Label für Furter Branch
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / könnte 3 beiliegende Teile enthalten
    • first the 2nd: [^"]*,\?\|"[^",]*",\?Übereinstimmung für eine Zeichenfolge ohne Anführungszeichen, möglicherweise gefolgt von einem Koma oder einer Zeichenfolge, die von zwei Anführungszeichen ohne Koma und möglicherweise gefolgt von einem Koma eingeschlossen ist.
    • als der erste RE-Teil besteht aus so vielen Wiederholungen des zuvor beschriebenen Teils 2, gefolgt von 1 Anführungszeichen und einigen Zeichen, aber keinen Anführungszeichen oder Komas.
    • Dem ersten RE-Teil folgt ein Koma.
    • Nota, der Rest der Linie muss nicht berührt werden
  • taführt eine Schleife durch, :awenn sich der vorherige s/Befehl geändert hat.
F. Hauri
quelle
Funktioniert auch mit verschachtelten Anführungszeichen. Super, danke!
Tricasse
5

Eine allgemeine Lösung, die auch mehrere Kommas zwischen ausgeglichenen Anführungszeichen verarbeiten kann, erfordert eine verschachtelte Ersetzung. Ich implementiere eine Lösung in Perl, die jede Zeile einer bestimmten Eingabe verarbeitet und in jedem anderen Paar von Anführungszeichen nur Kommas ersetzt:

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

oder kurz gesagt

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

Sie können entweder den zu verarbeitenden Text an den Befehl weiterleiten oder die zu verarbeitende Textdatei als letztes Befehlszeilenargument angeben.

user1146332
quelle
1
Das [^\\]wird den unerwünschten Effekt haben , der das letzte Zeichen innerhalb der Anführungszeichen passend und es (nicht \ Zeichen) entfernen, das heißt, sollten Sie verbrauchen nicht diesen Charakter. Versuchen Sie es (?<!\\)stattdessen.
tojrobinson
Vielen Dank für Ihren Einwand, ich habe das korrigiert. Trotzdem denke ich, dass wir hier nicht hinter die Behauptung schauen müssen, oder?
user1146332
1
Wenn Sie das nicht \ in Ihre Erfassungsgruppe aufnehmen, erhalten Sie ein gleichwertiges Ergebnis. +1
tojrobinson
1
+1. Nachdem ich ein paar Dinge mit sed ausprobiert hatte, überprüfte ich die Dokumente von sed und bestätigte, dass es nicht möglich ist, nur den passenden Teil einer Zeile zu ersetzen. Also gab ich es auf und versuchte es mit Perl. Endete mit einem sehr ähnlichen Ansatz aber nutzt diese Version [^"]*das Spiel nicht gierig zu machen (dh passt alles von einem "zum nächsten " ): perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'. Es erkennt nicht die ausgefallene Idee, dass ein Zitat mit einem Backslash entkommen könnte :-)
cas
Vielen Dank für Ihren Kommentar. Wäre interessant, wenn entweder der [^"]*Ansatz oder der explizite nicht-gierige Ansatz weniger CPU-Zeit verbraucht.
user1146332
3

Ich würde eine Sprache mit einem richtigen CSV-Parser verwenden. Beispielsweise:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file
Glenn Jackman
quelle
Obwohl mir diese Lösung anfangs gefiel, stellte sich heraus, dass sie für große Dateien unglaublich langsam war ...
KIC,
3

Ihre zweiten Anführungszeichen sind falsch:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

Die Verwendung von regulären Ausdrücken entspricht außerdem in der Regel dem längsten Teil des Texts. Dies bedeutet, dass dies nicht funktioniert, wenn Sie mehr als ein Feld in Anführungszeichen in der Zeichenfolge haben.

Eine Methode, mit der mehrere in Anführungszeichen gesetzte Felder behandelt werden

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Dies ist auch eine Möglichkeit, dies zu lösen. Bei Eingaben, die mehr als ein Komma pro Feld in Anführungszeichen enthalten können, müsste der erste Ausdruck im sed so oft wie der maximale Komma-Inhalt in einem einzelnen Feld wiederholt werden oder bis dahin ändert die Ausgabe überhaupt nicht.

Das Ausführen von sed mit mehr als einem Ausdruck sollte effizienter sein als das Ausführen mehrerer sed-Prozesse und eines "tr", das alle mit offenen Pipes ausgeführt wird.

Dies kann jedoch unerwünschte Folgen haben, wenn die Eingabe nicht richtig formatiert ist. dh geschachtelte Anführungszeichen, nicht abgeschlossene Anführungszeichen.

Mit dem laufenden Beispiel:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Ausgabe:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME
Didi Kohen
quelle
Sie können es allgemeinere machen mit bedingten Verzweigungen und besser lesbar mit ERE, zB mit GNU sed: sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'.
Thor
2

In Perl können Sie dies folgendermaßen Text::CSVanalysieren:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

Sie können mit drucken, Text::CSVaber in diesem Fall bleiben die Anführungszeichen in der Regel erhalten. (Obwohl, ich würde vorschlagen , - anstatt Strippen Angebote für die Ausgabe, können Sie einfach analysieren mit Text::CSVan erster Stelle).

Sobrique
quelle
0

Ich habe eine Funktion erstellt, die alle Zeichen in der Zeichenfolge durchläuft.
Wenn das Zeichen ein Anführungszeichen ist, wird das Häkchen (b_in_qt) als wahr markiert.
Während b_in_qt wahr ist, werden alle Kommas durch ein Leerzeichen ersetzt.
b_in_qt wird auf false gesetzt, wenn das nächste Komma gefunden wird.

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
user143598
quelle