取代一組或多組花括號外側的幾個逗號,並且一組或多組花括號中的例外情況

取代一組或多組花括號外側的幾個逗號,並且一組或多組花括號中的例外情況

在文字檔中我有多筆記錄。每筆記錄都有多個以逗號分隔的列,某些列有一組大括號,而其他列有多個大括號。

我需要:

  1. 如果在一組或多組大括號之外發現逗號,則應將逗號替換為豎線。

  2. 如果在一組或多組花括號內發現逗號,則應保留該逗號。所以給出的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})

解釋:

我將正規表示式分成兩部分來解釋。

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

第 1 部分

(\{(?:[^{}]|(?1))*\})
  • 只有當花括號正確配對時,此技巧才有效。
  • ()這些是捕獲組,用於捕獲字元。
  • \{匹配左大括號。
  • (?:[^{}]|(?1))

    • (?:...)稱為非捕獲組。
    • [^{}]它會匹配任何字符,但不匹配{}
    • |邏輯或運算符。
    • (?1)遞歸第一個捕獲組。
  • (?:[^{}]|(?1))*匹配前一個標記零次或多次。
  • \}結束}符號。

考慮下面的範例以及與其中的嵌套括號相符的模式。

細繩:

h{foo{bar}foobar}

圖案:

h(\{(?:[^{}]|(?1))*\})
  • 首先,正規表示式引擎嘗試匹配h(這是圖案中的) 針對輸入字串。所以第一個字母h是匹配的。
  • 尋找平衡括號的模式被輸入到捕獲組。
  • 現在,引擎採用\{模式中的第二個字元(即 )並嘗試與輸入字串進行比對。所以第一個{得到了被捕獲。我使用“捕獲”一詞而不是“匹配”,因為\{它位於捕獲組內。
  • (?:[^{}]|(?1))*這告訴正規表示式引擎符合除 {或之外的任何字元}零次或多次。如果您找到任何{}字符,則再次遞歸到第一個捕獲組。所以現在字串foo被捕獲了。接下來的字元是{,因此它遞歸到第一個捕獲組。現在,正規表示式引擎的遞歸等級下降了一級。我們的第一個捕獲組中的第一個模式是什麼(看正規表示式)?是的\{,現在它{與字串後面的符號相符foo
  • 引擎的遞歸深度仍然是一層,模式再次(?:[^{}]|(?1))*與字串相符bar。現在baris之後的字符},因此在匹配字串後bar,正則表達式引擎將不會進入,(?1)這就是我們使非捕獲組重複的原因或更多次。下一個模式(後的模式(?:[^{}]|(?1))*) 在正規表示式中是\}.所以這\}將匹配}緊接在 to 之後的大括號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聲明。但是,它應該尊重級聯。這是一行:

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,但大多數都在極端情況下失敗了。然而,有一個卻沒有:

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

使用定界符跨越一行數據,您可以確定在一行中找不到定界符 - 換行符。它從左到右沿線行走,停在兩個興趣點中的下一個——角色}{。當它停在 a 處時,{它會在其分隔符號中添加一個換行符;當在 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)

相關內容