1組以上の中括弧の外側にある複数のカンマと、1組以上の中括弧内の例外を置き換えます。

1組以上の中括弧の外側にある複数のカンマと、1組以上の中括弧内の例外を置き換えます。

テキスト ファイルには複数のレコードがあります。各レコードにはカンマで区切られた複数の列があり、一部の列には中括弧が 1 セットあり、他の列には複数の中括弧があります。

私は欲しい:

  1. 1 つ以上の中括弧の外側にカンマが見つかった場合は、カンマをパイプに置き換える必要があります。

  2. 1 組以上の中括弧内にカンマがある場合は、カンマはそのままにしておきます。したがって、THING1,{THING2,{THING3,}},THING4出力は次のようになりますTHING1|{THING2,{THING3,}}|THING4

サンプルレコード:

(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})

結果は次のようになります:

(**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})

答え1

これは単にPerl+ のregex組み合わせで実行できます。

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

例:

$ 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})

説明:

説明のために正規表現を 2 つの部分に分割しました。

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

第1部

(\{(?:[^{}]|(?1))*\})
  • このトリックは、中括弧が適切にペアになっている場合にのみ機能します。
  • ()これらは、文字をキャプチャするために使用されるキャプチャ グループです。
  • \{開き中括弧と一致します。
  • (?:[^{}]|(?1))

    • (?:...)非キャプチャグループと呼ばれます。
    • [^{}]任意の文字に一致しますが、またはは一致しません{}
    • |論理 OR 演算子。
    • (?1)最初のキャプチャ グループを再帰します。
  • (?:[^{}]|(?1))*前のトークンと 0 回以上一致します。
  • \}終了}記号。

以下の例と、その中のネストされた括弧に一致するパターンを検討してください。

弦:

h{foo{bar}foobar}

パターン:

h(\{(?:[^{}]|(?1))*\})
  • まず正規表現エンジンはhパターンにあった) を入力文字列と比較します。最初の文字hが一致しました。
  • バランスの取れた括弧を見つけるためのパターンがキャプチャ グループに入力されます。
  • エンジンは\{パターンの2番目の文字(つまり)を取り、入力文字列と照合します。最初の{文字は捕らえられた\{はキャプチャ グループ内にあるため、match ではなく captured という単語を使用しました。
  • (?:[^{}]|(?1))*これは、正規表現エンジンに、 {または以外の任意の文字を}0 回以上一致させるように指示します。{または}文字が見つかった場合は、最初のキャプチャ グループにもう一度再帰します。これで文字列がfooキャプチャされました。次の文字は なので{、最初のキャプチャ グループに再帰します。これで、正規表現エンジンは再帰で 1 レベル下がっています。最初のキャプチャ グループの最初のパターンは何でしょうか (正規表現を参照)? です。これで、直後にあった記号が文字列 に\{一致します。{foo
  • エンジンは再帰の1レベル深く、パターンは再び(?:[^{}]|(?1))*文字列に一致しますbar。 の後の文字はbarなので}、文字列に一致した後bar、正規表現エンジンは に入りません。(?1)そのため、非キャプチャグループを繰り返し作成しました。ゼロまたはそれ以上の回数。次のパターン(パターン後(?:[^{}]|(?1))*正規表現内の ) は です\}。したがって、これはの直後の中括弧\}と一致します。これで、正規表現エンジンは再帰の 1 レベル深くから抜け出し、パターンは次の文字列と一致します。Last は最後の中括弧と一致します。}bar[^{}]*foobar\}
  • これで、最初のキャプチャ グループに が含まれます{foo{bar}foobar}

第二部

  • (*SKIP)(*F)一致またはキャプチャされた文字が失敗します。したがって、このケースでは、キャプチャされたバランスの取れた中括弧はすべてスキップされました。つまり、正規表現エンジンに残りの文字列の文字を一致させるように強制します。
  • 構文またはフォーマット(*SKIP)(*F)

        part1(*SKIP)(*F)|part2
         |                  |
     |----                  -----> Match this
    Don't match this 
    
  • したがって、直後のパターンは、|残りの文字列の文字と一致させようとします(ネストされた中括弧を除く文字列)。

  • この場合、 の後のパターンは|です,。そのため、ネストされた中括弧の外側にあるすべてのカンマが一致しました。

読むこれを理解するRegular Expression Recursion

注記:

  • (?R)サブパターン全体、つまりマッチ全体を再帰します。(?R)次のように書くこともできます。(?0)
  • (?1)最初のサブパターン(つまり、最初のキャプチャグループ内のパターン)を再帰します。

答え2

交換する

,{ 

|{ 

そして

}, 

}|

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

結果的に

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

答え3

難しい言い方ですが、恐れる必要はありませんsed。しかし、カスケードを尊重する必要があります。1 行で説明すると次のようになります。

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

コメント付きバージョンは次のとおりです。

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

それで、望み通りの出力が得られました。

答え4

でこれを行う方法をいくつか考えましたsedが、ほとんどは特殊なケースで機能しませんでした。ただし、次の 1 つは機能しません。

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

これは、行に見つからない区切り文字 (改行文字) を使用して、データ行を横切ります。行を左から右に移動し、2 つの注目ポイント (文字 ) のいずれかの次のポイントで停止します}{。 で停止すると、{区切り文字に改行文字が追加されます。で停止すると}、2 つの改行文字がある場合は 1 つが減算されます。

行に改行文字が 1 つしかなく、区切り文字の後に a または a の前にコンマが続く時点で停止すると、{}それをパイプに置き換えて再帰的に戻り、同じ置換テストを再度試行します。

これは、必要に応じて不均衡な中括弧グループも保護するはずですが、引用符付きの中括弧を処理する方法を採用していません。引用符付きの中括弧を処理するには、興味がある点そうだと思います。でも、それを知ることにそれほど興奮はしません。

サンプルからの出力:

(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)

関連情報