Confundido pela saída sed ao usar N. Alguém pode explicar esses resultados?

8

Estou aprendendo sed. Tudo parecia estar indo bem até me deparar com o N (multi-linha seguinte). Eu criei este arquivo (guide.txt) para fins de prática / compreensão / contexto. Aqui está o conteúdo do dito arquivo ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Portanto, meu objetivo é substituir TODAS as instâncias de "Administrador da rede" por "Usuário do sistema". Como a primeira instância de "Administrador de rede" é separada por uma nova linha (\ n), preciso que o operador próximo de várias linhas (N) acrescente a linha que começa com "Administrador" com a linha anterior terminando com "Rede \ n" . Sem problemas. Mas eu também quero pegar todas as outras instâncias de linha única do "Network Administrator".

Da minha pesquisa, aprendi que precisarei de dois comandos de substituição; uma para a string separada por nova linha e outra para as outras. Além disso, há algumas coisas acontecendo por causa da última linha contendo a correspondência de substituição e a linha múltipla seguinte. Então eu ofereço isso ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Isso retorna esses resultados ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Eu pensei que a substituição de linha única pegaria todas as instâncias "normais" de "Network Administrator" e trocaria por "System User", enquanto a instrução multi-line funcionaria com sua mágica na instância separada por nova linha, mas como você pode ver, ele retornou, o que eu considero, resultados inesperados.

Depois de alguns mexer, eu caí nessa ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

E voilà, eu recebo a saída desejada de ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Por que isso funciona e o script sed original não funciona? Eu realmente quero entender isso.

Agradecemos antecipadamente por qualquer ajuda.

    
por dlowrie290 11.10.2017 / 04:32

2 respostas

6

Como você está aprendendo sed , aproveito o tempo para adicionar à resposta do @ John1024:

1) Por favor, note que você está usando \n na string de substituição. Isso funciona no GNU sed , mas não faz parte do POSIX, portanto, ele inserirá uma barra invertida e um n em muitos outros sed s (usando \n no padrão é portátil, btw).

Em vez disso, sugiro fazer s/Network\([[:space:]]\)Administrator/SystemUs‌​er/g : O [[:space:]] corresponderá a nova linha ou espaço em branco, para que você não precise de dois comandos s , mas combine-os em um. Envolvendo-o com \(...\) você pode se referir a ele na substituição: O será substituído pelo que quer que tenha sido correspondido no primeiro par de \(\) .

2) Para corresponder corretamente os padrões em duas linhas, você deve saber o padrão N;P;D :

 sed '$!N;s/Network\([[:space:]]\)Administrator/SystemUser/g;P;D'

O N está sempre anexando a próxima linha (exceto a última linha, é por isso que ela é "endereçada" com $! (= se não a última linha; você deve sempre considerar preceder N com $! para evitar terminar o script acidentalmente. Depois, após a substituição, o P imprime somente a primeira linha no espaço padrão e a D exclui essa linha e inicia o próximo ciclo com os restos do espaço padrão (sem ler a próxima Esta é provavelmente a que você originalmente pretendia.

Lembre-se desse padrão, você geralmente precisará dele.

3) Outro padrão útil para edição de múltiplas linhas, especialmente quando mais de duas linhas estão envolvidas: Mantenha a coleta de espaço, como sugeri a John:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/SystemUs‌​er/g'

Repito para explicar: H acrescenta cada linha ao espaço de espera. Como isso resultaria em uma nova linha extra antes da primeira linha, a primeira linha precisa ser movida em vez de anexada com 1h . O seguinte $!d significa "para todas as linhas, exceto a última, exclua o espaço do padrão e comece novamente". Assim, o resto do script é executado apenas para a última linha. Neste ponto, todo o arquivo é coletado no espaço de espera (portanto, não use isso para arquivos muito grandes!) E o g o move para o espaço de padrão, para que você possa fazer todas as substituições de uma vez como pode com a opção -z do GNU sed .

Este é outro padrão útil que sugiro ter em mente.

    
por 11.10.2017 / 08:27
7

Primeiro, observe que sua solução realmente não funciona. Considere este arquivo de teste:

$ cat test1
Network
Administrator Network
Administrator

E, em seguida, execute o comando:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

O problema é que o código não substitui o último Network\nAdministrator .

Esta solução funciona:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

Também podemos aplicar isso ao seu guide.txt :

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

A chave é continuar lendo em linhas até encontrar uma que não termine com Network . Quando isso é feito, as substituições podem ser feitas.

Nota de Compatibilidade: Todos os itens acima usam \n no texto de substituição. Isso requer o GNU sed. Não funcionará no BSD / OSX sed.

[Dica do chapéu para Philippos .]

Versão multilinha

Se isso ajudar a esclarecer, aqui está o mesmo comando dividido em várias linhas:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Como funciona

  1. :a

    Isso cria um rótulo a .

  2. /Network$/{ $!{N;ba} }

    Se esta linha terminar com Network , então, se isto não for , a última linha ( $! ) lerá e anexará a próxima linha ( N ) e voltará ao rótulo a ( ba ).

  3. s/Network\nAdministrator/System\nUser/g

    Faça a substituição com a nova linha intermediária.

  4. s/Network Administrator/System User/g

    Faça a substituição com o branco intermediário.

Solução mais simples (somente GNU)

Com o GNU sed ( não BSD / OSX), precisamos apenas de um comando substituto:

$ sed -zE 's/Network([[:space:]]+)Administrator/SystemUser/g' test1
System
User System
User

E no arquivo guide.txt :

$ sed -zE 's/Network([[:space:]]+)Administrator/SystemUser/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Nesse caso, -z informa ao sed para ler até o primeiro caractere NUL. Como os arquivos de texto nunca têm um caractere nulo, isso tem o efeito de ler o arquivo inteiro de uma só vez. Podemos então fazer a substituição sem nos preocuparmos em perder uma linha.

Este método não é bom se o arquivo for grande (geralmente significa gigabytes). Se for tão grande, então ler tudo de uma vez pode sobrecarregar a RAM do sistema.

Solução que funciona tanto no GNU quanto no BSD sed

Como sugerido por Phillipos , o seguinte é uma solução portátil:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/SystemUs‌​er/g'
    
por 11.10.2017 / 04:47

Tags