假设我有一个像这样的字符串
[[["q", "0"], "R"], "L"], ["q", [["1", "["], "]"]], [["q", ["2", "L"]], "R"], ["q", ["3", ["R", "L"]]]
我想从中删除所有嵌套的括号
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
我了解如何编写一种算法,通过压入和弹出堆栈或仅递增和递减计数器来实现此目的,但我很好奇是否有一种方法可以仅使用sed
或等基本工具来完成此操作awk
。
答案1
bracket.awk
:
BEGIN{quote=1}
{
for(i=1;i<=length;i++){
ch=substr($0,i,1)
pr=1
if(ch=="\""){quote=!quote}
else if(ch=="[" && quote){brk++;pr=brk<2}
else if(ch=="]" && quote){brk--;pr=brk<1}
if(pr){printf "%s",ch}
}
print ""
}
$ awk -f bracket.awk file
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
其背后的想法:
初始化quote=1
.按字符读取文件。每当找到引用时,就反转quote
变量(如果1
,则变为0
,反之亦然)。
quote
然后,根据计数器,仅当设置为 1 时才对括号进行计数,并且不会打印多余的括号brk
。
该print ""
语句只是添加换行符,上面printf
没有这样做。
答案2
和perl
:
perl -pe '
s{([^]["]+|"[^"]*")|\[(?0)*\]}
{$1 // "[". ($& =~ s/("[^"]*"|[^]["]+)|./$1/gr) . "]"}ge'
这利用了perl
的递归正则表达式。
外部s{regex}{replacement-code}ge
将输入标记为:
[
除,]
或之外的任何字符序列"
- 带引号的字符串
- 一个
[...]
组(在正则表达式中使用递归来查找匹配]
)
然后,如果该标记位于前两个类别 ( $1
) 中,我们将其替换为自身,如果不是带有非引号 的标记[
,]
则在内部替换中使用相同的标记化技术将其删除。
要处理转义引号和引号内的反斜杠(例如"foo\"bar\\"
),请替换[^"]
为(?:[^\\"]|\\.)
。
和sed
如果您sed
支持-E
或-r
选项来使用扩展正则表达式而不是基本的你可以用循环来完成,[...]
首先替换最里面的 s :
LC_ALL=C sed -E '
:1
s/^(("[^"]*"|[^"])*\[("[^"]*"|[^]"])*)\[(("[^"]*"|[^]["])*)\]/\1\4/
t1'
(用于LC_ALL=C
加速它并使其相当于perl
在将字节解释为字符时也忽略用户区域设置的那个)。
POSIXly,它应该仍然可以通过以下方式实现:
LC_ALL=C sed '
:1
s/^\(\(\("[^"]*"\)*[^"]*\)*\[\(\("[^"]*"\)*[^]"]*\)*\)\[\(\(\("[^"]*"\)*[^]["]*\)*\)\]/\1\6/
t1'
这里使用代替\(\(a\)*\(b\)*\)*
的(a|b)*
基本正则表达式没有交替运算符(某些sed
实现的 BRE 有\|
这样的运算符,但这不是 POSIX/可移植的)。
答案3
我只是发布了这个替代方案,因为你说:
我了解如何编写一种算法,通过压入和弹出堆栈或仅递增和递减计数器来实现此目的
实际上我只会使用计数器。
$ cat tst.awk
{
$0 = encode($0)
sep = ""
while ( match($0,/\[[^][]+]/) ) {
if ( prevRstart && (RSTART > prevRstart) ) {
printf "%s%s", sep, decode(prevStr)
sep = ", "
}
prevStr = substr($0,RSTART,RLENGTH)
prevRstart = RSTART
$0 = substr($0,1,RSTART-1) "<" substr($0,RSTART+1,RLENGTH-2) ">" substr($0,RSTART+RLENGTH)
}
printf "%s%s\n", sep, decode(prevStr)
}
function encode(str) {
gsub(/@/,"@A",str)
gsub(/[{]/,"@B",str)
gsub(/}/,"@C",str)
gsub(/</,"@D",str)
gsub(/>/,"@E",str)
gsub(/"\["/,"{",str)
gsub(/"]"/,"}",str)
return str
}
function decode(str) {
gsub(/[<>]/,"",str)
gsub(/}/,"\"]\"",str)
gsub(/[{]/,"\"[\"",str)
gsub(/@E/,">",str)
gsub(/@D/,"<",str)
gsub(/@C/,"}",str)
gsub(/@B/,"{",str)
gsub(/@A/,"@",str)
return str
}
。
$ awk -f tst.awk file
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
看https://stackoverflow.com/a/35708616/1745001了解这些 sub() (在该问题中是 sed)正在做什么来根据需要对这些有意义的字符和字符串进行编码/解码,以便能够隔离字符串[...]
。
所以 - 它所做的是[...]
从内到外查找字符串,换句话说,给定匹配项[ [ foo ] ]
,然后我们更改to和to ,以便下次循环时匹配整个字符串。然后我们只需在打印之前删除和。当下一次循环时,它知道它找到了最外层,匹配字符串从超出前一个起始位置的位置开始(即不在前一个匹配字符串内),此时它会打印前一个匹配字符串。match("[ [ foo ] ]",/[[^][]/)
[ foo ]
[
<
]
>
match("[ < foo > ]",/[[^][]/)
<
>
[ foo ]
[...]
答案4
可以用 sed 来完成:
sed -E ':a;s/(\[[^][]*)\[([^][]*)\]([^][]*\])/\1\2\3/;ta'
这个想法是匹配一个[ ]
对,在它内部,匹配该对以删除[ ]
它,反过来,不包含[
或]
。为了避免匹配一个[
或一个,]
我们需要使用[^][]*
.这在几个地方重复:
(\[[^][]*)
匹配(并捕获)一个[
后接多个非[
或 的]
。\[
随后是一个[
([^][]*)
接下来是匹配并捕获几个非[
或]
。\]
随后是一个]
([^][]*\])
后面跟几个non[
或]
以a结尾]
。
然后替换整个捕获,从而\1\2\3
删除内部[]
对。
:a
如果进行了更改,请用标签和循环包围上面的所有内容ta
,并重复替换,直到[]
找不到更多内部对(并被替换)。