모든 괄호를 제거하고 중첩된 괄호만 제거하는 방법이 있습니까?

모든 괄호를 제거하고 중첩된 괄호만 제거하는 방법이 있습니까?

다음과 같은 문자열이 있다고 가정해 보겠습니다.

[[["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)*에는 대체 연산자가 없습니다( sedBRE의 일부 구현에는 \|이러한 연산자가 있지만 이는 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'

아이디어는 [ ]그 안에서 일치하는 쌍을 일치시켜 [ ]이를 제거하고 결과적으로 [또는 를 포함하지 않는 쌍을 일치시키는 것입니다 ]. 하나 [또는 하나 의 일치를 방지하려면 ]다음을 사용해야 합니다 [^][]*. 이는 여러 곳에서 반복됩니다.

  • (\[[^][]*)여러 개의 NOR이 [뒤따르는 1개를 일치(및 캡처)합니다 .[]
  • \[그 다음에[
  • ([^][]*)[다음은 여러 NOT -OR을 일치시키고 캡처하는 것입니다 ].
  • \]그 다음에]
  • ([^][]*\])[그 뒤에는 .]]

그런 다음 전체 캡처가 교체되어 \1\2\3내부 []쌍이 제거됩니다.

:a변경하는 경우 위의 모든 항목을 레이블 및 루프로 묶고 더 이상 내부 쌍이 발견되지 않을 ta때까지 교체를 반복합니다 .[]

관련 정보