Substitua cada 6º tubo no powershell

6

Percebo que estou fazendo uma pergunta semelhante que já foi feita e respondida, mas não consegui extrapolar a resposta de que precisava, pois o mecanismo regex e regex é diferente o suficiente. Eu tenho logs de gerenciamento de ativos de hardware que são delimitados por canal, mas não são delimitados principalmente entre os nós de extremidade. Os logs são assim:

|STATUS1|HOSTNAME1|IP1|MAC1|IS_WIRED1|STATUS2|HOSTNAME2|IP2|MAC2|IS_WIRED2|STATUS3|HOSTNAME3|IP3|MAC3|IS_WIRED3

O que eu gostaria de fazer é substituir a cada 6 | por um retorno de carro assim:

|STATUS1|HOSTNAME1|IP1|MAC1|IS_WIRED1
|STATUS2|HOSTNAME2|IP2|MAC2|IS_WIRED2
|STATUS3|HOSTNAME3|IP3|MAC3|IS_WIRED3

O mais próximo que obtive seleciona cada endpoint, mas não tenho certeza de como utilizá-lo usando o powershell.

[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*

Estou familiarizado com o comando replace no PS e estou imaginando que o resultado final seria algo para esse efeito:

$hosts = $hosts -replace "<highspeed_low_drag_velcro_snap_regex_here>","\r\n"

Obrigado antecipadamente!

    
por Tensore 03.01.2018 / 03:23

1 resposta

8

Ok, então este é realmente um pouco complicado. Indiscutivelmente, o regex não é a melhor ferramenta para o trabalho, mas pode fazê-lo.

-replace "(?<=^((\|[^|]*){5})+)\|","'n|"

Vou tentar orientá-lo:

  • Seu texto tem uma seção que você deseja corresponder e uma seção que você deseja substituir . Tradicionalmente, regex substitui toda a string de pesquisa, então você usaria um grupo de captura para especificar alguma parte da string de pesquisa para ser clonado para a saída de substituição. Outra maneira é usar um lookaround , que foi o que fiz aqui. O PowerShell (.NET) é uma das poucas linguagens regex que suporta lookbehinds de comprimento variável , então estamos com sorte.
  • A seção (?<=) é um lookbehind. Isso significa que tudo entre o = e o ) é correspondido , mas não substituído . Portanto, ^((\|[^|]*){5})+ é usado como uma condição - a substituição só acontecerá se esse bit corresponder ao texto antes da substituição pretendida.
  • A seção ^((\|[^|]*){5})*[^|]* pode ser resumida como "desde o início da linha ( ^ ), corresponder conjuntos de cinco | s e depois corresponder ao texto até o próximo | ".
    • O início da linha ^ é importante - caso contrário, pode corresponder a qualquer ponto da linha e não há garantia de quantos | s vieram antes.
    • Como | tem um significado especial na regex, ele precisa ter escape: \| . Não precisa ser escapado quando estiver dentro de uma classe de caracteres ( [] ).
    • [^|]* significa "texto até o próximo | " - mais tecnicamente, "quantos caracteres forem diferentes de | " - mais tecnicamente "repita a classe de caractere [^|] quantas vezes for possível, onde esse caractere class corresponde a qualquer caractere diferente de | ".
    • * significa "zero ou mais repetições do caractere anterior, o maior número possível"
    • Portanto, (\|[^|]*) significa correspondência | seguido pelo maior número possível de caracteres até o próximo | . Isto irá corresponder a |text
    • {5} significa repetir o token anterior exatamente 5 vezes. É exatamente equivalente a copiar e colar o token anterior 5 vezes. Então, isso vai corresponder a |text|text|text|text|text
    • ((\|[^|]*){5})+ é uma ou mais repetições desse grupo inteiro. Por isso, pode corresponder a |text|text|text|text|text , |text|text|text|text|text|text|text|text|text|text , etc. - em múltiplos de 5. A razão pela qual usamos + em vez de * é que não queremos corresponder ao grupo vazio e substituir o primeiro | .
    • E isso faz todo o lookbehind, o que significa que só substituirá um | por um múltiplo de 5 | s atrás dele, desde o início da linha.
  • Após isso, com um \| como o texto real a ser substituído, precedido pela aparência correspondente.
  • Tomando seu exemplo |STATUS1|HOSTNAME1|IP1|MAC1|IS_WIRED1|STATUS2|HOSTNAME2|IP2|MAC2|IS_WIRED2|STATUS3|HOSTNAME3|IP3|MAC3|IS_WIRED3 , ele corresponderá ao seguinte:

    |STATUS1|HOSTNAME1|IP1|MAC1|IS_WIRED1**|**STATUS2|HOSTNAME2|IP2|MAC2|IS_WIRED2**|**STATUS3|HOSTNAME3|IP3|MAC3|IS_WIRED3
    

Você notará aqui (se ainda não o fez) que está realmente tentando substituir todos os 5 | menos o primeiro, nem todo . Mas o método lookbehind manipula a situação "menos o primeiro" de forma bastante clara.

E agora a string de substituição.

  • Como este é o PowerShell, quando queremos \n , na verdade queremos 'n porque o caractere de escape do PowerShell é ' . Note que isto é necessário apenas na string de substituição; na regex em si você ainda usaria \n para passar essa sequência literal para o mecanismo regex.
  • E como você tem um líder | em cada linha, precisamos adicionar um novo | após a nova linha. Isso funciona porque suas linhas originais não terminam com | , portanto, não há nada a substituir no final das linhas, portanto, não terminamos com uma nova linha extra nem perdemos | .

Se você preferir o método de grupo de captura mais tradicional:

-replace "((?:[^|]+\|){4}[^|]+)\|","'$1'n|"

Descobrir como isso funciona é deixado como um exercício para o leitor;) Dica: o $1 backreference deve ser escapado (com ' ) porque senão o PowerShell o interpreta como uma variável shell.

    
por 03.01.2018 / 04:32