在文本文件中我有多条记录。每条记录都有多个以逗号分隔的列,某些列有一组大括号,而其他列有多个大括号。
我需要:
如果在一组或多组大括号之外发现逗号,则应将逗号替换为竖线。
如果在一组或多组花括号内发现逗号,则应保留该逗号。所以给出的
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))*\})
(*SKIP)(*F)|,
第 1 部分
(\{(?:[^{}]|(?1))*\})
- 仅当花括号正确配对时,此技巧才有效。
()
这些是捕获组,用于捕获字符。\{
匹配左大括号。(?:[^{}]|(?1))
(?:...)
称为非捕获组。[^{}]
它会匹配任何字符,但不匹配{
或}
|
逻辑或运算符。(?1)
递归第一个捕获组。
(?:[^{}]|(?1))*
匹配前一个标记零次或多次。\}
结束}
符号。
考虑下面的示例以及与其中的嵌套括号匹配的模式。
细绳:
h{foo{bar}foobar}
图案:
h(\{(?:[^{}]|(?1))*\})
- 首先,正则表达式引擎尝试匹配
h
(这是图案中的) 针对输入字符串。所以第一个字母h
是匹配的。 - 寻找平衡括号的模式被输入到捕获组中。
- 现在,引擎采用
\{
模式中的第二个字符(即 )并尝试与输入字符串进行匹配。所以第一个{
得到了被捕获。我使用“捕获”一词而不是“匹配”,因为\{
它位于捕获组内。 (?:[^{}]|(?1))*
这告诉正则表达式引擎匹配除{
或之外的任何字符}
零次或多次。如果您找到任何{
或}
字符,则再次递归到第一个捕获组。所以现在字符串foo
被捕获了。接下来的字符是{
,因此它递归到第一个捕获组。现在,正则表达式引擎的递归级别下降了一级。我们的第一个捕获组中的第一个模式是什么(查看正则表达式)?是的\{
,现在它{
与字符串后面的符号相匹配foo
。- 引擎的递归深度仍然是一层,模式再次
(?:[^{}]|(?1))*
与字符串匹配bar
。现在bar
is之后的字符}
,因此在匹配字符串后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)