Ersetzen Sie mehrere Kommas außerhalb einer oder mehrerer geschweifter Klammern und Ausnahmen, die in einer oder mehreren geschweiften Klammern stehen.

Ersetzen Sie mehrere Kommas außerhalb einer oder mehrerer geschweifter Klammern und Ausnahmen, die in einer oder mehreren geschweiften Klammern stehen.

In der Textdatei habe ich mehrere Datensätze. Jeder Datensatz hat mehrere durch Kommas getrennte Spalten, einige Spalten haben einen Satz geschweifter Klammern und andere haben mehr als eine geschweifte Klammer.

Ich brauche:

  1. Wenn ein Komma außerhalb einer oder mehrerer geschweifter Klammern steht, sollte das Komma durch ein Pipe-Zeichen ersetzt werden.

  2. Wenn ein Komma in einer oder mehreren geschweiften Klammern steht, sollte das Komma unverändert bleiben. THING1,{THING2,{THING3,}},THING4Die Ausgabe sollte also lauten THING1|{THING2,{THING3,}}|THING4:

Beispieldatensatz:

(999969,2500,"777777888",0,"45265","65522",NULL,10001,2014-09-15 10:27:07.287,2014-09-15 10:28:49.085,2014-09-15 06:28:50.000,0,0,NULL,"text","401c4133091977",{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL},NULL,{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]},{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1},NULL,NULL,NULL,NULL,NULL,{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

das Ergebnis sollte sein:

(**999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|**{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}**|NULL|**{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}**|**{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}**|NULL|NULL|NULL|NULL|NULL|**{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

Antwort1

Dies kann einfach durch die Kombination Perl„+“ erfolgen regex.

perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file

Beispiel:

$ perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file
(999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}|NULL|{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}|{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}|NULL|NULL|NULL|NULL|NULL|{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

Erläuterung:

Zur Erklärung habe ich den regulären Ausdruck in zwei Teile aufgeteilt.

  1. (\{(?:[^{}]|(?1))*\})
  2. (*SKIP)(*F)|,

1. Teil

(\{(?:[^{}]|(?1))*\})
  • Dieser Trick funktioniert nur, wenn die geschweiften Klammern richtig gepaart sind.
  • ()Dies sind Erfassungsgruppen, die zum Erfassen von Zeichen verwendet werden.
  • \{entspricht einer öffnenden geschweiften Klammer.
  • (?:[^{}]|(?1))

    • (?:...)Wird als nicht erfassende Gruppe bezeichnet.
    • [^{}]Es würde mit jedem Zeichen übereinstimmen, jedoch nicht mit {oder}
    • |Logischer ODER-Operator.
    • (?1)Rekursiert die erste Erfassungsgruppe.
  • (?:[^{}]|(?1))*Passt null oder mehrmals zum vorherigen Token.
  • \}Schließsymbol }.

Betrachten Sie das folgende Beispiel und das Muster, das den verschachtelten Klammern darin entspricht.

Zeichenfolge:

h{foo{bar}foobar}

Muster:

h(\{(?:[^{}]|(?1))*\})
  • Zuerst versucht die Regex-Engine, das h(das war im Muster) mit der Eingabezeichenfolge verglichen. Der erste Buchstabe hwurde also abgeglichen.
  • Muster zum Finden der ausgeglichenen Klammern werden in eine Erfassungsgruppe eingespeist.
  • Nun nimmt die Engine das zweite Zeichen (also \{) im Muster und versucht, es mit der Eingabezeichenfolge abzugleichen. Das erste {wurde alsogefangen. Ich habe das Wort „erfasst“ anstelle von „übereinstimmend“ verwendet, da \{es sich innerhalb einer Erfassungsgruppe befindet.
  • (?:[^{}]|(?1))*{Dies weist die Regex-Engine an, jedes Zeichen außer oder }null oder mehrmals abzugleichen . Wenn Sie ein beliebiges {oder }Zeichen gefunden haben, gehen Sie noch einmal zur ersten Erfassungsgruppe zurück. Jetzt foowurde die Zeichenfolge also erfasst. Das folgende Zeichen ist {, also geht es zur ersten Erfassungsgruppe zurück. Jetzt ist die Regex-Engine eine Ebene tiefer in der Rekursion. Was ist das erste Muster in unserer ersten Erfassungsgruppe (siehe den regulären Ausdruck)? Es ist \{, jetzt entspricht es dem {Symbol, das direkt nach der Zeichenfolge kam foo.
  • Die Engine ist immer noch eine Ebene tief in der Rekursion, wieder (?:[^{}]|(?1))*stimmt das Muster mit der Zeichenfolge überein bar. Jetzt ist das Zeichen nach dem bar, }also kommt die Regex-Engine nach dem Abgleichen der Zeichenfolge barnicht hinein, (?1)deshalb haben wir die nicht erfassende Gruppe so erstellt, dass sie wiederholt wirdnulloder mehrmals. Nächstes Muster (Muster nach zu(?:[^{}]|(?1))*) im regulären Ausdruck ist \}. Dies \}würde also mit der Klammer übereinstimmen }, die direkt nach to stand bar. Jetzt verlässt die Regex-Engine die Rekursion eine Ebene tiefer und das Muster [^{}]*würde mit der folgenden Zeichenfolge übereinstimmen foobar. Last \}würde mit der letzten geschweiften Klammer übereinstimmen.
  • Jetzt enthält unsere erste Erfassungsgruppe {foo{bar}foobar}.

2. Teil

  • (*SKIP)(*F)Bewirkt, dass die Zeichen, die abgeglichen oder erfasst werden, fehlschlagen. In unserem Fall wurden also alle erfassten geschweiften Klammern übersprungen. Das heißt, es zwingt die Regex-Engine, die Zeichen aus der verbleibenden Zeichenfolge abzugleichen.
  • Syntax oder Format von(*SKIP)(*F)

        part1(*SKIP)(*F)|part2
         |                  |
     |----                  -----> Match this
    Don't match this 
    
  • Das Muster, das direkt danach kam, |versucht also, die Zeichen aus der verbleibenden Zeichenfolge abzugleichen (Zeichenfolge außer den verschachtelten Klammern).

  • |In unserem Fall ist das Muster nach dem ,. Daher werden alle Kommas außerhalb der verschachtelten Klammern abgeglichen.

LesenDasum das zu verstehen Regular Expression Recursion.

Notiz:

  • (?R)rekursiert das gesamte Untermuster, d. h. die gesamte Übereinstimmung. Wir könnten es auch (?R)so schreiben:(?0)
  • (?1)rekursiert das erste Untermuster (also das Muster innerhalb der ersten Erfassungsgruppe)

Antwort2

ersetzen

,{ 

mit

|{ 

Und

}, 

mit

}|

 echo "THING1,{THING2,{THING3,}},THING4" | sed -re "s/,\{/|{/gi" | sed -re "s/},/}|/gi"

führt zu

THING1|{THING2|{THING3,}}|THING4

Antwort3

Keine Angst, das ist eine harte sedAussage. Aber sie sollte die Kaskadierung berücksichtigen. Hier ist sie in einer Zeile:

sed -e 's/,/|/g;:a;s/{\([^{}]*\)|\([^{}]*\)}/{\1,\2}/g;ta;s/{\([^{}]*\)}/<\1>/g;ta;:b;s/<\([^<>]*\)>/{\1}/g;tb' file

Hier ist die kommentierte Version:

sed -e '
        s/,/|/g;                                 #replaces all commas (,) with pipes (|)
        :a;                                      #sets a label called a
            s/{\([^{}]*\)|\([^{}]*\)}/{\1,\2}/g; #replaces {a|b|c} with {a,b|c}
          ta;                                    #go back to the label `a` and repeat the
                                                 #prevous part until there is nothing more
                                                 #to replace: when {a|b|c} became {a,b,c}
          s/{\([^{}]*\)}/<\1>/g;                 #replace {...} with <...>
        ta;                                      #go back to label a again until all {} are 
                                                 #replaces by <>
        :b;                                      #create a new label called b
          s/<\([^<>]*\)>/{\1}/g;                 #replace <...> back to {...}
        tb;                                      #and back to label b to repeat the previous
                                                 #part
' file

Damit habe ich die gewünschte Ausgabe erhalten.

Antwort4

Mir sind ein paar Möglichkeiten eingefallen, dies in zu tun sed, aber die meisten scheiterten in Sonderfällen. Eine funktioniert jedoch nicht:

sed 's/^/\n/;:b
/\n\n/!s/\(\n[^,{}]*\),/\1|/;tb
s/\(\n\n*\)\([^{}]*[{}]\)/\2\1/
s/{\(\n\)/&\1/;s/\(}\n\)\n/\1/;tb
s/\n//g' <<\DATA
(999969,2500,"777777888",0,"45265","65522",NULL,10001,2014-09-15 10:27:07.287,2014-09-15 10:28:49.085,2014-09-15 06:28:50.000,0,0,NULL,"text","401c4133091977",{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL},NULL,{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]},{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1},NULL,NULL,NULL,NULL,NULL,{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL},poopoer,sciioooper)
DATA

Dabei wird eine Datenzeile mithilfe eines Trennzeichens durchlaufen, das Sie mit Sicherheit nicht in einer Zeile finden werden - ein Zeilenumbruchzeichen. Es durchläuft die Zeile von links nach rechts und hält am nächsten von zwei interessanten Punkten an - den Zeichen }{. Wenn es bei a anhält, {fügt es seinem Trennzeichen ein Zeilenumbruchzeichen hinzu; wenn es bei a anhält, }zieht es eins ab, wenn zwei vorhanden sind.

Wenn es an einem Punkt angehalten wird, an dem nur ein Zeilenumbruchzeichen in der Zeile zu finden ist und auf das Trennzeichen vor einem Komma folgt, {}wird es durch ein Pipe-Zeichen ersetzt und es wird rekursiv zurückgegangen, um denselben Ersetzungstest erneut zu versuchen.

Dies sollte bei Bedarf auch unausgeglichene Klammergruppen schützen, obwohl es keine Methode zur Behandlung einer Klammer in Anführungszeichen verwendet, was durch Hinzufügen vonSehenswürdigkeitenIch schätze schon, aber ich bin nicht besonders begeistert, es herauszufinden.

Die Ausgabe Ihres Beispiels:

(999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}|NULL|{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}|{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}|NULL|NULL|NULL|NULL|NULL|{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL}|poopoer|sciioooper)

verwandte Informationen