vimを使用してjsonから大きなチャンクを削除する

vimを使用してjsonから大きなチャンクを削除する

私は巨大なjsonファイル(50万行)を持っています。

特定の文字列を含む項目セットを削除する必要があります。

{
    "bla1": {
        "Part1": "Plop1",
        "Part2": "Plop2",
        "Part3": "BadFling1<stuff>",
        "part4": "Plop4",
    },
    "bla2": {
        "Part1": "Plop1",
        "Part2": "Plop2",
        "Part3": "<stuff>",
        "part4": "Plop4",
    },
    // etc for many more entries
}

すべてのアイテムには、「Part3」アイテムの前に「BadFling1」が付いています。

「BadFling1」を含むすべてのアイテムを自動的に削除する最良の方法を知りたいです。たとえば、上記から誤った項目を削除した結果は次のとおりです。

{
    "bla2": {
        "Part1": "Plop1",
        "Part2": "Plop2",
        "Part3": "<stuff>",
        "part4": "Plop4",
    },
    // etc for many more entries
}

最初の試みは成功しましたが、十分に高速ではありませんでした(少し手作業でしたので)。

/BadFling1
qan3k5ddq
:map z n@a

今「z」キーを押し続けます。

私のvim fooが十分に強力ではないので、vimでプロセスをよりよく自動化する方法がわかりません。助けてくれてありがとう。

Bashの代替案(他のコマンドラインツールも歓迎します)。

答え1

この試みvim

:g/BadFling/normal [{V]}d

これにより、:globalパターンに一致するすべての行でコマンドが実行されます(BadFling例として使用しました。必要に応じて調整します)。この場合、実行されるコマンドは通常:normalモードコマンドを実行するコマンドと同じです。これの目的は、角かっこペア間の移動および移動コマンド機能を利用することです[{。 is の]} vim組み合わせは、Vd1 行ずつ削除するために使用されます。これはJSONパーサーほど強力ではありませんが、各"blah1"セクションが独自の行セットに含まれていると仮定することができるため、行ごとに削除しても誤って他のブロックに属する項目は削除されません。たとえば、次のような場合、1行ずつ削除する方法は機能しません。

    ... end of block you want to keep
}, "blah1" : {
    block you want removed
}, "blah2" : {
    start of block you want to keep ...
}

また、[{直接親ブロックのみを使用するため、より多くのネストレベルがある場合は機能しません。

答え2

あなたのバージョンが十分に新しい場合は、grep以下を使用して実行できます。diffdiff

ire@localhost$ grep -B 3 -A 2 BadFling1 huge.json | diff --changed-group-format="%>" --unchanged-group-format="" - huge.json 
{
    "bla2": {
        "Part1": "Plop1",
        "Part2": "Plop2",
        "Part3": "<stuff>",
        "part4": "Plop4",
    },
    // etc for many more entries
}

grep一致する項目の周囲の行を抽出して、誤った履歴を削除します。diff元のバージョンからそのアイテムを削除してください。説明で述べたように、このソリューションを使用するには、ブロックサイズが一貫しており、一致する行が各ブロック内の同じ場所にある必要があります(例と同様)。

そうでない場合(レコードのサイズが異なる場合、またはレコード要素の位置を信頼できない場合)、高速解析スクリプトを作成するためのヒントにします。 JSONパーサーが組み込まれているPythonの数行だけを使用すると、これらのレコードを簡単かつ安全に削除できます。

答え3

awkの解決策は次のとおりです。

awk '/".*":\ {/             { open=line; skip_block=0 }
     /"Part3":\ "BadFling1/ { skip_block=1 }
     /},/                   { if (skip_block) { line=open; next } }
     { lines[line++]=$0 }
     END { for (i=0;i<=line;i++) { print lines[i] } }' yourfile > clean

まだ正しくテストされていませんが、始めるのに役立ちます。ブロックの長さが可変であっても機能し、ブロック内の未定義行がどこにあるかは重要ではありません。

説明する:

行1:この行がブロックの先頭と一致する場合は、配列の位置を記録して、これまでのブロックを良好であるとマークします。

行2:この行が未定義の行と一致する場合はブロックを表示します。

行3:マッチブロックの終わりです。ブロックが表示されたら、配列の位置をブロックが始まる位置にリセットし、次の行にジャンプします。

行4:現在の行を配列に追加し、行カウンタをインクリメントします。

行5:ファイルを読み取った後、「良い」ブロックのみを含む配列を印刷します。

Bashでも同じ機能が得られますが、awkははるかに高速です。私の考えでは、「より重い」言語を使わずにこれがまさにawkの目的です。

答え4

精力使用:

:%s/BadFling1//g

「BadFling1」が検索されると、すべて「」に置き換えられます。

関連情報