有没有办法删除所有的括号,而只删除嵌套的括号?

有没有办法删除所有的括号,而只删除嵌套的括号?

假设我有一个像这样的字符串

[[["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,并重复替换,直到[]找不到更多内部对(并被替换)。

相关内容