In einer Textdatei möchte ich ,
(Kommas) und auch "
(Anführungszeichen) entfernen (nur wenn die Anführungszeichen durch Kommas getrennte Zahlen enthalten).
56,72,"12,34,54",x,y,"foo,a,b,bar"
Erwartete Ausgabe
56,72,123454,x,y,"foo,a,b,bar"
Notiz:Ich zeige die obige Zeile nur als Beispiel. Meine Textdatei enthält viele Zeilen wie oben und die durch Kommas getrennten Zahlen innerhalb der Anführungszeichen sollten variieren. Das heißt,
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
Erwartete Ausgabe:
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
n
In den Anführungszeichen sind mehrere Zahlen durch Kommas getrennt. Lassen Sie die Anführungszeichen, die Zeichen enthalten, so wie sie sind .
Ich liebe sed
Textverarbeitungstools. Ich freue mich, wenn Sie eine sed
Lösung dafür posten.
Antwort1
Wenn Perl ok ist, hier eine kurze (und wahrscheinlich schnelle, wenn auch nicht unbedingt einfache :) ) Vorgehensweise:
perl -pe 's:"(\d[\d,]+)":$1=~y/,//dr:eg' file
Das e
Flag für den s:::
Operator (das nur eine andere Schreibweise für ist s///
) bewirkt, dass der Ersatz als Ausdruck behandelt wird, der jedes Mal ausgewertet wird. Dieser Ausdruck nimmt die $1
Erfassung aus dem regulären Ausdruck (dem bereits die Anführungszeichen fehlen) und übersetzt ( y///
, was auch als geschrieben werden kann ), indem alle Kommas tr///
gelöscht ( ) werden. Das Flag für ist erforderlich, damit der Wert die übersetzte Zeichenfolge ist, statt der Anzahl der Übersetzungen./d
r
y
Für diejenigen, die sich durch Perl irgendwie beschmutzt fühlen, hier das Python-Äquivalent. Python ist eigentlich kein Shell-Einzeiler-Tool, aber manchmal kann man es zur Kooperation überreden. Das Folgende kann als eine Zeile geschrieben werden (im Gegensatz zu for
Schleifen, die nicht geschrieben werden können), aber das horizontale Scrollen macht es (noch) unleserlicher:
python -c '
import re;
import sys;
r=re.compile("\"(\d+(,\d+)*)\"");
all(not sys.stdout.write(r.sub(lambda m:m.group(1).replace(",",""),l))
for l in sys.stdin)
' < file
Antwort2
Dies (adaptiert vonHier) sollte tun, was Sie brauchen, obwohl @ricis Perl-Version viel einfacher ist:
$ sed -r ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;ta; s/""/","/g;
s/"([0-9]*)",?/\1,/g ' file
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454,
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
Erläuterung
:a
: Definieren Sie ein Label mit dem Namena
.s/(("[0-9,]*",?)*"[0-9,]*),/\1/
: Das muss abgebaut werden- Verwenden Sie zunächst diese Konstruktion:
(foo(bar))
,\1
wird seinfoobar
und\2
wird seinbar
. "[0-9,]*",?
: Übereinstimmung mit 0 oder mehr von0-9
oder,
, gefolgt von 0 oder 1,
.("[0-9,]*",?)*
: Übereinstimmung mit 0 oder mehr der oben genannten."[0-9,]*
: Übereinstimmung mit 0 oder mehr von0-9
oder,
die direkt nach einem kommen"
- Verwenden Sie zunächst diese Konstruktion:
ta;
: Gehe zurück zum Etiketta
und führe es erneut ausWenndie Ersetzung war erfolgreich.s/""/","/g;
: Nachbearbeitung. Ersetzen""
durch","
.s/"([0-9]*)",?/\1,/g
: Entfernen Sie alle Anführungszeichen um Zahlen.
Dies lässt sich möglicherweise anhand eines anderen Beispiels leichter verstehen:
$ echo '"1,2,3,4"' | sed -nr ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;p;ta;'
"1,2,34"
"1,234"
"1234"
"1234"
Sie können also eine Zahl finden, die direkt nach einem Anführungszeichen steht und auf die ein Komma und eine weitere Zahl folgen. Fügen Sie die beiden Zahlen zusammen und wiederholen Sie den Vorgang, bis dies nicht mehr möglich ist.
An dieser Stelle halte ich es für sinnvoll, ein Zitat aus info sed
dem Abschnitt zu erwähnen, in dem erweiterte Funktionen beschrieben werden, wie z. B. das oben verwendete Label (danke fürs Auffinden, @Braiam):
In den meisten Fällen weist die Verwendung dieser Befehle darauf hin, dass Sie wahrscheinlich besser mit einer Sprache wie „awk“ oder Perl programmieren.
Antwort3
Für CSV-Daten würde ich eine Sprache mit einem echten CSV-Parser verwenden. Zum Beispiel mit Ruby:
ruby -rcsv -pe '
row = CSV::parse_line($_).map {|e| e.delete!(",") if e =~ /^[\d,]+$/; e}
$_ = CSV::generate_line(row)
' <<END
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
END
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
Antwort4
Verwenden vonRaku(früher bekannt als Perl_6)
~$ raku -pe 's:g/ \" ~ \" (\d+) ** 2..* % "," /{$0.join}/;' file
Beispieleingabe:
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
Beispielausgabe:
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
Raku ist eine Programmiersprache aus der Perl-Familie mit einer Reihe leistungsstarker Regex-Funktionen. Einen allgemeinen Überblick über diese Antwort finden Sie unter der folgenden URL:
https://unix.stackexchange.com/a/722570/227738
Im obigen Code werden Ziffern erkannt und eingebettete Kommas entfernt. Der reguläre Ausdruck nutzt die Tatsache aus, dassverschachtelte Strukturenkann mit Rakus neuer ~ Tilde-Notation (verschachtelt) gekennzeichnet werden, was \" ~ \" [\d+]
„eine oder mehrere Ziffern umgeben von Anführungszeichen“ bedeutet.
Zusätzlich,sich wiederholende Strukturenkann mit Rakus neuem %
modifizierten Quantifizierer für wiederholte Strukturen bezeichnet werden. Die Notation [\d+] ** 2..* % "," bedeutet „eine oder mehrere durch ,
Kommas getrennte Ziffern, wobei dieses Muster ** 2..*
zwei oder mehr Mal wiederholt wird. [Wenn es zufällig ein nachfolgendes Trennzeichen (z. B. Komma) gibt, verwenden Sie in der Syntax a %%
statt of ].%
Dies ist nur ein Anfang. CSV-Dateien mit alternativen Trennzeichen, eingebetteten Zeilenumbrüchen, eingebetteten Kommas, möglicherweise leeren Feldern usw. müssen wirklich von einem echten CSV-Parser wie dem Raku-Modul verarbeitet werden Text::CSV
. Weitere Informationen finden Sie unter den folgenden Links.
https://docs.raku.org/language/regexes
https://raku.land/github:Tux/Text::CSV
https://raku.org