다음과 같은 문자열이 있다고 가정해 보겠습니다.
[[["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'
아이디어는 [ ]
그 안에서 일치하는 쌍을 일치시켜 [ ]
이를 제거하고 결과적으로 [
또는 를 포함하지 않는 쌍을 일치시키는 것입니다 ]
. 하나 [
또는 하나 의 일치를 방지하려면 ]
다음을 사용해야 합니다 [^][]*
. 이는 여러 곳에서 반복됩니다.
(\[[^][]*)
여러 개의 NOR이[
뒤따르는 1개를 일치(및 캡처)합니다 .[
]
\[
그 다음에[
([^][]*)
[
다음은 여러 NOT -OR을 일치시키고 캡처하는 것입니다]
.\]
그 다음에]
([^][]*\])
[
그 뒤에는 .]
]
그런 다음 전체 캡처가 교체되어 \1\2\3
내부 []
쌍이 제거됩니다.
:a
변경하는 경우 위의 모든 항목을 레이블 및 루프로 묶고 더 이상 내부 쌍이 발견되지 않을 ta
때까지 교체를 반복합니다 .[]