替换一组或多组花括号外侧的几个逗号,并且一组或多组花括号中的例外情况

替换一组或多组花括号外侧的几个逗号,并且一组或多组花括号中的例外情况

在文本文件中我有多条记录。每条记录都有多个以逗号分隔的列,某些列有一组大括号,而其他列有多个大括号。

我需要:

  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)

相关内容