Substituir uma string contendo caracteres de nova linha

10

Com o shell bash , em um arquivo com linhas como as seguintes

first "line"
<second>line and so on

Gostaria de substituir uma ou mais ocorrências de "line"\n<second> por other characters e obter cada vez:

first other characters line and so on

Portanto, preciso substituir uma string por caracteres especiais, como " e < , e por um caractere de nova linha.

Após pesquisar entre as outras respostas, descobri que sed pode aceitar novas linhas no lado direito do comando (portanto, a other characters string), mas não na esquerda.

Existe uma maneira (mais simples que this ) para obter este resultado com sed ou grep ?

    
por BowPark 21.10.2014 / 19:26

4 respostas

3

Três comandos sed diferentes:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Todos os três compilam o comando básico s/// ubstitution:

s/"[^"]*"\n<[^>]*>/other characters /

Eles também tentam tomar cuidado ao manipular a última linha, pois sed s tende a diferir em sua saída nos casos de borda. Esse é o significado de $! , que é um endereço que corresponde a todas as linhas que são ! e não o $ por último.

Eles também usam o comando N ext para anexar a próxima linha de entrada ao espaço padrão seguindo um caractere \n ewline. Qualquer um que tenha estado sed ing por um tempo terá aprendido a confiar no caractere \n ewline - porque a única maneira de obter um é explicitamente colocá-lo lá.

Todos os três tentam ler o mínimo possível de informações antes de executar uma ação - sed age assim que pode e não precisa ler em um arquivo de entrada inteiro antes de fazer isso.

Apesar de todos fazerem N , todos os três diferem em seus métodos de recursão.

Primeiro Comando

O primeiro comando emprega um loop N;P;D muito simples. Esses três comandos são embutidos em qualquer sed compatível com POSIX e se complementam bem.

  • N - como já mencionado, acrescenta a linha de entrada N ext ao espaço padrão seguindo um delimitador% e_line% ewline inserido.
  • \n - como P ; ele p rints pattern-space - mas apenas até o primeiro caractere P ewline. E assim, dada a seguinte entrada / comando:

    • \n
  • printf %s\n one two | sed '$!N;P;d' sed rints apenas um . No entanto, com ...

  • P - como D ; ele d elimina o espaço padrão e inicia outro ciclo de linha. Ao contrário de D , d exclui apenas até a primeira D ewline no padrão de espaço. Se houver mais no espaço padrão seguindo o caractere \n ewline, \n iniciará o próximo ciclo de linha com o que resta. Se o sed no exemplo anterior fosse substituído por d , por exemplo, D seria sed rint ambos um e dois .

Este comando recursa apenas para linhas que não correspondem à declaração P ubstitution. Como o s/// ubstitution remove o s/// ewline adicionado com \n , nunca há nada restante quando N sed elimina o espaço padrão.

Testes podem ser feitos para aplicar os D e / ou P seletivamente, mas existem outros comandos que se encaixam melhor nessa estratégia. Como a recursão é implementada para lidar com linhas consecutivas que correspondem apenas a parte da regra de substituição, sequências consecutivas de linhas correspondentes a ambas terminam da D ubstitution não funcionam bem. :

Dada essa entrada:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... imprime ...

first other characters "line"
<second>other characters line and so on

No entanto, ele lida com

first "line"
second "line"
<second>line

... muito bem.

Segundo comando

Este comando é muito semelhante ao terceiro. Ambos empregam um rótulo s/// ranch / :b est (como também é demonstrado na resposta de Joeseph R. aqui ) e recorre a ele dadas certas condições.

  • Os scripts t - portable -e :n -e delimitarão uma definição de rótulo sed com uma instrução : eww ou uma nova instrução \n xecução inline.
    • -e - define um rótulo chamado :n . Isso pode ser retornado a qualquer momento com n ou bn .
  • tn - o comando tn est retorna a um rótulo especificado (ou, se nenhum for fornecido, encerra o script para o ciclo de linha atual) se houver t ubstitution desde o rótulo foi definido ou desde que foi chamado pela última vez s/// ests bem-sucedido.

Neste comando, a recursão ocorre para as linhas correspondentes. Se t substituir com êxito o padrão por outros caracteres , sed retornará ao rótulo sed e tentará novamente. Se uma :n ubstitution não for executada s/// autoprints padrão-espaço e iniciar o próximo ciclo de linha.

Isso tende a lidar com sequências consecutivas melhor. Onde o último falhou, isso imprime:

first other characters other characters other characters line and so on

Terceiro Comando

Como mencionado, a lógica aqui é muito semelhante à anterior, mas o teste é mais explícito.

  • sed - este é o teste de /"$/bn . Como o comando sed ranch é uma função desse endereço, b será apenas sed ranch de volta para b depois que um :n ewline for anexado e o espaço padrão ainda terminar com \n aspas duplas .

Existe tão pouco quanto possível entre " e N - dessa forma, b pode coletar exatamente o máximo de entradas necessárias para garantir que a linha a seguir não corresponda à sua regra. O sed ubstitution difere aqui, pois emprega o sinalizador s/// lobal - e, portanto, fará todas as substituições necessárias de uma só vez. Dada a entrada idêntica, este comando é enviado de forma idêntica ao último.

    
por 21.10.2014 / 21:06
7

Bem, posso pensar em algumas maneiras simples, mas nenhuma delas envolve grep (que não faz substituições) ou sed .

  1. Perl

    Para substituir cada ocorrência de "line"\n<second> com other characters , use:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Ou, para tratar várias ocorrências consecutivas de "line"\n<second> como uma e substituir todas elas por um único other characters , use:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Exemplo:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    O -00 faz com que Perl leia o arquivo no "modo de parágrafo", o que significa que "linhas" são definidas por \n\n em vez de \n , essencialmente, cada parágrafo é tratado como uma linha. A substituição, portanto, corresponde a uma nova linha.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    A mesma ideia básica, definimos o separador de registro ( RS ) como \n\n para fazer o slurp do arquivo inteiro, depois o separador de registro de saída para nada (caso contrário, uma nova linha extra é impressa) e use o sub() função para fazer a substituição.

por 21.10.2014 / 19:49
6

leia o arquivo inteiro e faça uma substituição global:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
    
por 21.10.2014 / 20:57
3

Aqui está uma variante na resposta de Glenn que funcionará se você tiver várias ocorrências consecutivas (funciona com o GNU sed apenas):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

O :x é apenas um rótulo para ramificação. Basicamente, o que isto faz, é que ele verifica a linha após a substituição e se ela ainda corresponde a "line" , ramifica de volta para o :x label (é o que o bx faz) e adiciona outra linha ao buffer e inicia o processamento isso.

    
por 21.10.2014 / 23:27