すべての括弧を削除し、入れ子になった括弧のみを削除する方法はありますか?

すべての括弧を削除し、入れ子になった括弧のみを削除する方法はありますか?

次のような文字列があるとしましょう。

[[["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入力を次のように表示します。

  • []またはを除くすべての文字シーケンス"
  • 引用符付き文字列
  • グループ[...](正規表現で再帰を使用して一致を見つける]

次に、トークンが最初の2つのカテゴリ()にある場合はそれ$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'

アイデアは、[ ]その中で一致するペアを一致させてそれらを[ ]削除し、結果的にまたはを[含まないペアを一致させることです]。 1[つまたは1つの一致を防ぐには、]次のものを使用する必要があります[^][]*。これはいくつかの場所で繰り返されます。

  • (\[[^][]*)複数のNORが[続く1つを一致(およびキャプチャ)します。[]
  • \[それから[
  • ([^][]*)[以下は、複数のNOT -ORを一致させてキャプチャすることです]
  • \]それから]
  • ([^][]*\])[その後は]]

その後、キャプチャ全体が置き換えられ、\1\2\3内部[]ペアが削除されます。

:a変更した場合は、上記のすべての項目をラベルとループで囲み、内部ペアが見つからなくなるtaまで交換を繰り返します。[]

関連情報