Remover grandes pedaços de json usando vim

3

Eu tenho um arquivo enorme (1/2 milhão de linhas) de json.

Preciso excluir um conjunto de entradas que contenha uma determinada string.

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

Todas as entradas têm "BadFling1" como o prefixo da entrada "Part3".

Eu queria saber como a melhor maneira de automatizar a remoção de todas as entradas que incluem "BadFling1". Por exemplo, o resultado da remoção da entrada incorreta acima é:

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

Minha primeira tentativa estava funcionando, mas não foi rápida o suficiente (já que era um pouco manual).

/BadFling1
qan3k5ddq
:map z n@a

Agora, mantenha pressionada a tecla "z".

Meu vim foo não é strong o suficiente, então não tenho certeza de como automatizar melhor os processos no vim. Qualquer ajuda apreciada.

Abordagens alternativas no bash (ou outras ferramentas de linha de comando também são bem-vindas).

    
por Martin York 17.12.2012 / 21:19

4 respostas

5

Tente isso em vim :

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

O comando :global executa um comando em todas as linhas que correspondem ao padrão (usei BadFling como exemplo - ajuste-o, se necessário). O comando que está sendo executado neste caso é o comando :normal , que executa comandos no modo normal. O objetivo disso é usar o poder dos comandos [{ e ]} vim movement que se movem entre os pares de chaves. A combinação Vd is é usada para fazer uma exclusão de linha. Isso não é tão robusto quanto um analisador JSON, mas funciona assumindo que cada "blah1" parte está contida em seu próprio conjunto de linhas e, portanto, uma exclusão de linha não excluirá acidentalmente nada que pertença a outro bloco. Por exemplo, a abordagem de exclusão de linha não funcionaria se você tivesse algo como

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

Além disso, o [{ usa apenas o bloco pai imediato, portanto, se você tiver mais níveis de aninhamento, também não funcionará.

    
por 17.12.2012 / 22:12
2

Você pode fazer isso com grep e diff , se sua versão de diff for recente o suficiente:

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
}

O grep extrai os registros ruins, extraindo as linhas que cercam sua correspondência. O diff remove-os do original. Como mencionado nos comentários, essa solução exige que os tamanhos dos blocos sejam consistentes e que a linha correspondente esteja no mesmo lugar em cada bloco (como no seu exemplo).

Se não fosse esse o caso (tamanho de registro variável ou posicionamento não confiável de elementos de registro), eu tomaria isso como uma sugestão para escrever um script de análise rápida. Você pode facilmente e com segurança remover esses registros com apenas algumas linhas do Python, que possui um analisador JSON integrado.

    
por 17.12.2012 / 21:50
1

Aqui está uma solução no 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

Isso não é muito bem testado, mas deve começar. Ele funcionará mesmo se os blocos forem de comprimento variável e não se importar com o local em que a linha desqualificante está posicionada.

Explicação:

linha 1: se a linha corresponder ao início de um bloco, anote a posição na matriz, marque o bloco como bom até o momento

linha 2: se a linha corresponder à linha desqualificante e marcar o bloco

linha 3: corresponde ao final de um bloco. se o bloco estiver marcado, redefina a posição na matriz para onde o bloco começou e pule para a próxima linha

linha 4: adicione a linha atual ao array e incremente o contador de linha

linha 5: quando terminar de ler o arquivo, imprima o array, contendo apenas os blocos "bons"

Você pode implementar a mesma coisa no bash, mas o awk será muito mais rápido e, na minha opinião, esse é o tipo de coisas que o awk é construído, sem ter que usar uma linguagem "mais pesada".

    
por 17.12.2012 / 23:26
0

usando o vim:

:%s/BadFling1//g

pesquisará todas as ocorrências de "BadFling1" e substituirá por "".

    
por 17.12.2012 / 21:52

Tags