Pesquisa e substituição multilinhas

9

Gostaria de realizar uma pesquisa e substituir em um arquivo por 12000 linhas.

Especificamente, se ^ SetFontSize 28existir uma ocorrência após um ^Hidebloco e antes do próximo ^Hideou ^Show, mude 28para 18.

Aqui está um trecho do arquivo original.

Hide # Gear - Endgame
    ItemLevel >= 77
    Rarity = Magic
    LinkedSockets >= 3
    BaseType "Runic Hatchet"
    SetTextColor 140 190 255 # Magic Item Highlight
    SetFontSize 28

Hide # Gear - Endgame
    ItemLevel >= 77
    Rarity = Magic
    Sockets >= 3
    BaseType "Runic Hatchet"
    SetTextColor 140 190 255 # Magic Item Highlight
    SetFontSize 28

Show # Gear - Endgame
    ItemLevel >= 83
    Rarity = Normal
    Sockets < 3
    BaseType "Tiger Hook"
    SetTextColor 240 240 240 # Normal Item Highlight
    SetBackgroundColor 70 70 70
    SetFontSize 28

O resultado final de um dos Hideblocos deve ficar assim:

Hide # Gear - Endgame
    ItemLevel >= 77
    Rarity = Magic
    LinkedSockets >= 3
    BaseType "Runic Hatchet"
    SetTextColor 140 190 255 # Magic Item Highlight
    SetFontSize 18

Substituindo SetFontSize 28para SetFontSize 18, mas apenas se ele aparecer em um ^Hidebloco.

O regex desagradável que tentei foi: :%s/^Hide\(.*\)SetFontSize 28$/Hide\1SetFontSize 18/g

Mas foi dito que o padrão não foi encontrado. Informe-me se alguma informação adicional é necessária ou se minha solicitação não está clara.

Cara
fonte
5
Todo Hidebloco tem uma SetFontSizelinha (qualquer que seja o valor)? Em caso afirmativo, você poderia usar #:%s/Hide\_.\{-\}SetFontSize \zs28/18/
muru
2
O @muru whatever be the valuecausaria problemas, sua solução só funciona se cada Hidebloco tiver uma SetFontSizelinha e seu valor for exatamente 28, caso contrário, ele corresponderá até 28outro bloco.
dedowsdi

Respostas:

3

Uma maneira de resolver isso seria usar o :globalque gera uma saída de faixa.

O uso típico do globalcomando seria

:[range]g/{pattern}/[cmd]

Ele também tem a opção de fazer esse padrão gerar um intervalo, em vez de uma correspondência de linha única, usando ,o formato de

:[range]g/{first pattern}/,/{second pattern}/[cmd]

Isso gera um intervalo que é aplicado ao comando.

Para o seu exemplo, o primeiro padrão seria combinar a primeira Hideentrada e o segundo padrão ou é Hide, Showou o fim do arquivo (supondo que você quer que a última Esconder caso).

:g/Hide/,/\(Hide\|Show\|\%$\)/s/SetFontSize 28/SetFontSize 18/

O primeiro regex é simples /Hide/,. O segundo regex contém algumas partes interessantes.

  • \(e \)cria um agrupamento de átomos para corresponder
  • \| é a operação OU
  • \%$ representa o final do arquivo

Depois de definirmos nossos intervalos, aplicamos substituto com um padrão e uma sequência como você faria normalmente.

Observe que o regex usado neste exemplo é muito básico. Você quer ter certeza de que seus identificadores para o início e o fim do intervalo capturem as áreas certas.

jecxjo
fonte
3

Parece que o seu desagradável regex não foi suficiente desagradável ... :-)

Seção de Pesquisa

A pesquisa teria que ser alterada para isso:

^Hide\(\(\(Show\|Hide\)\@!\_.\)*\)SetFontSize 28

Isso inclui algumas coisas incomuns e tantos parênteses ... Vamos ver o que temos lá:

O sinal de intercalação ( ^)

O sinal de intercalação é usado para significar o início da linha. Acho que já estamos familiarizados com este.

Um ponto importante, o ^não funciona, exceto como o primeiro caractere em seu padrão. Após o que é tomado literalmente. Para incluir um início de linha em sua expressão, você precisa usar \_^. No entanto, em nossa situação, não precisamos disso.

(Há um fenômeno semelhante com $e \_$)

O primeiro e o último parênteses ( \( ... \))

O primeiro e o último parênteses são usados por si próprios, o que significa que ele pega o que aparece dentro e o define como parâmetro \1. Você já usou isso em seu próprio regex, portanto, assumirei que você também esteja familiarizado com este.

O segundo conjunto de parênteses

Como você pode notar, há um segundo conjunto de parênteses seguido por um asterisco \( ... \)*. Isso significa que estamos procurando o que corresponder inúmeras vezes. Essa é a maneira usual de usar o asterisco, para que você esteja familiarizado com ele.

O terceiro conjunto de parênteses, OR e \_.

Sim, na verdade existem três parênteses antes da palavra Show. Este último conjunto é necessário por dois motivos: o \|e o seguinte @!.

Em relação à operação OR, você já deve estar familiarizado com ela.

Show\|Hide    or    Hide\|Show

O pedido não importa aqui. O \é necessário na frente do |para trabalhar no vim.

Os parênteses em torno dessa expressão nos permitem seguir a expressão de alguma forma . Aqui o @!.

\(Show\|Hide\)@!

Este é muito menos familiar. Significa se não for correspondido . Porém, o uso disso não é muito fácil, mas você precisa seguir essa expressão com o que deseja extrair, o que não deve corresponder à expressão. É por isso que temos \_.por trás desse padrão.

Os \_.meios correspondem a qualquer coisa. Ao contrário do que é o .próprio, o que não corresponde ao \npersonagem. Em outras palavras, correspondemos a qualquer caractere em qualquer número de linhas, a menos que corresponda a Showou Hide.

Observe que os parênteses em torno dessa expressão também são importantes, assim como o asterisco, portanto, é exatamente isso que a faz funcionar:

\(\(Show\|Hide\)@!\_.\)*

aka coincidir com o que quer para o próximo Showou Hidecaracteres (note que ele também iria corresponder Showing, Shower, HideMe, etc. você deve ser capaz de usar \<e \>, se for necessário para coincidir com a palavra exatamente.)

Nota lateral: para pesquisar em várias linhas, também é possível usar o \ncaractere no padrão. No entanto, não é tão versátil quanto o \_.padrão.

SetFontSize 28

Agora a seção também precisa incluir SetFontSize 28. Assim como você teve no seu regex. Se não SetFontSize 28aparecer nessa seção, tente a pesquisa novamente na próxima seção.

Devido à negação acima (a correspondência exceto Showou Hide), a pesquisa não vaza para a próxima seção, correndo o risco de estragar tudo.

Seção de substituição

A substituição é igual à sua:

.../Hide\1SetFontSize 18/

Usamos os parênteses na pesquisa para que \1funcione conforme o esperado.

Pesquisa completa e substituição

Os padrões resultantes são assim:

:%s/^Hide\(\(\(Show\|Hide\)@!\_.\)*\)SetFontSize 28/Hide\1SetFontSize 18/

O \(Show\|Hide\)deve incluir todos os cabeçalhos possíveis .

Fontes

Regex para corresponder a qualquer caractere, incluindo nova linha ( \_.\{-})

Procure linhas que não contenham padrão e outras pesquisas úteis ( @!)

Documentação do Vim: pattern ( \_^)

Alexis Wilke
fonte
1
Eu gosto da ()*, minha versão de sua resposta: %s/\v^Hide.*\n(\s+.*\n)*\s*SetFontSize\s+\zs28/16.
dedowsdi