Substituir uma sequência contendo caracteres de nova linha

10

Com o bashshell, 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>com other characterse obter a cada vez:

first other characters line and so on

Então, eu tenho que substituir uma string por caracteres especiais como "e <e por um caractere de nova linha.

Depois de pesquisar entre as outras respostas, descobri que é sedpossível aceitar novas linhas no lado direito do comando (portanto, a other charactersstring), mas não no esquerdo.

Existe uma maneira (mais simples que isso ) de obter esse resultado com sedou grep?

BowPark
fonte
você está trabalhando com um mac? a \ndeclaração ewline que você faz é por que eu pergunto. as pessoas raramente perguntam se podem fazer o s//\n/que você pode fazer com o GNU sed, embora a maioria dos outros sedrejeite essa fuga no lado direito. ainda assim, a \nfuga funcionará à esquerda em qualquer POSIX sede você poderá traduzi-las de forma portável, como y/c/\n/se ela tivesse o mesmo efeito s/c/\n/ge, portanto, nem sempre é tão útil.
mikeserv

Respostas:

3

Três sedcomandos 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 se baseiam no s///comando básico de ubstitution:

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

Todos eles também tentam tomar cuidado no manuseio da última linha, pois os seds tendem a diferir em sua saída em casos extremos. Esse é o significado de $!um endereço que corresponde a todas as linhas que !não são as $últimas.

Todos também usam o Ncomando ext para anexar a próxima linha de entrada ao espaço do padrão após um \ncaractere ewline. Qualquer pessoa que esteja sedhá algum tempo aprenderá a confiar no \npersonagem ewline - porque a única maneira de conseguir um é explicitamente colocá-lo lá.

Todos os três tentam ler o mínimo possível de informações antes de tomar uma ação - sedagem o mais rápido possível e não precisam ler um arquivo de entrada inteiro antes de fazê-lo.

Embora façam tudo N, todos os três diferem em seus métodos de recursão.

Primeiro Comando

O primeiro comando emprega um N;P;Dloop muito simples . Esses três comandos são integrados a qualquer POSIX compatível sede se complementam muito bem.

  • N- como já mencionado, anexa a Nlinha de entrada ext ao espaço padrão após um \ndelimitador de linha de linha inserido .
  • P- como p; ele Pcria espaço no padrão - mas apenas até o primeiro \ncaractere ewline que ocorre . E assim, dada a seguinte entrada / comando:

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

  • D- como d; ele Delimina o espaço padrão e inicia outro ciclo de linha. Ao contrário d , Dexclui apenas até a primeira linha de \new que ocorre no espaço do padrão. Se houver mais espaço no padrão após o \ncaractere ewline, sedinicia o próximo ciclo de linha com o que resta. Se o dno exemplo anterior foram substituídos com um D, por exemplo, sedseria PRint tanto um e dois .

Este comando ocorre apenas para linhas que não correspondem à s///instrução ubstitution. Como a ubstitution s///remove o \newline adicionado com N, nunca resta nada quando se sed Delimina o espaço do padrão.

Poderiam ser feitos testes para aplicar o Pe / ou Dseletivamente, mas existem outros comandos que se encaixam melhor nessa estratégia. Como a recursão é implementada para manipular linhas consecutivas que correspondem apenas a parte da regra de substituição, sequências consecutivas de linhas correspondentes às duas extremidades da s///substituição não funcionam bem .:

Dada esta 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, lida com

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

...bem.

Segundo comando

Este comando é muito semelhante ao terceiro. Ambos empregam um rótulo de :bfazenda / test (como também é demonstrado na resposta de Joeseph R. aqui ) e retornam a ele sob determinadas condições.

  • -e :n -e- sedscripts portáteis delimitarão uma :definição de rótulo com uma linha de \new ou uma nova -einstrução de xecution em linha .
    • :n- define um rótulo chamado n. Isso pode ser retornado a qualquer momento com bnou tn.
  • tn- o tcomando est retorna para um rótulo especificado (ou, se nenhum for fornecido, sai do script para o ciclo de linha atual) se houver alguma s///substituição desde que o rótulo foi definido ou desde a última vez que foi chamado tests com êxito.

Neste comando, a recursão ocorre para as linhas correspondentes. Se sedsubstituir com êxito o padrão por outros caracteres , sedretornará ao :nrótulo e tentará novamente. Se uma s///substituição não for executada, o sedespaço de padrão é impresso automaticamente e inicia o próximo ciclo de linha.

Isso tende a lidar melhor com seqüências consecutivas. 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.

  • /"$/bn- este é sedo teste. Como o bcomando ranch é uma função desse endereço, sedele bretornará somente :ndepois que um \newline for acrescentado e o espaço do padrão ainda terminar com "aspas duplas.

Há o mínimo possível entre Ne bquanto possível - dessa maneira, é sedpossível reunir rapidamente, exatamente, o máximo de entrada necessário para garantir que a linha a seguir não corresponda à sua regra. A s///ubstituição difere aqui porque emprega a gbandeira global - e, portanto, fará todas as substituições necessárias de uma só vez. Dada entrada idêntica, este comando é idêntico ao último.

mikeserv
fonte
Desculpe pela pergunta trivial, mas qual é o significado DATAe como você recebe a entrada de texto?
BowPark
@BowPark - Neste exemplo, <<\DATA\ntext input\nDATA\nestá inserido , mas esse é apenas o texto entregue sedpelo shell em um documento aqui . Funcionaria tão bem quanto sed 'script' filenameou process that writes to stdout | sed 'script'. Isso ajuda?
mikeserv
Sim, obrigado! Por que sem Dtoda linha modificada é dupla? (Você usou-o como é necessário, talvez eu não sei sedmuito bem)
BowPark
1
@BowPark - você recebe duplas ao omitir o arquivo Dporque, de Doutra forma, Delimina da saída o que agora vê dobrado. Acabei de fazer uma edição - e posso expandir isso também em breve.
mikeserv
1
@BowPark - ok, eu atualizei e forneci opções. Pode ser um pouco mais fácil de ler / entender agora. Também falei explicitamente sobre o Dassunto.
mikeserv
7

Bem, eu posso pensar em algumas maneiras simples, mas nenhuma delas envolve grep(o que não substitui de qualquer maneira) ou sed.

  1. Perl

    Para substituir cada ocorrência de "line"\n<second>por 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 "line"\n<second>como uma e substituir todas elas por uma única 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 -00Perl faz com que o arquivo seja lido no "modo de parágrafo", o que significa que "linhas" são definidas por, em \n\nvez de \n, essencialmente, cada parágrafo ser tratado como uma linha. A substituição corresponde, portanto, 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 idéia básica, configuramos o separador de registros ( RS) para \n\nreduzir o arquivo inteiro, depois o separador de registros de saída para nada (caso contrário, uma nova linha extra é impressa) e, em seguida, usamos a sub()função para fazer a substituição.

terdon
fonte
2
@mikeserv? Qual? O segundo deveria, o OP disse que quer "substituir uma ou mais ocorrências de", portanto, comer o parágrafo pode ser o que eles esperam.
terdon
ponto muito bom. Acho que me concentrei mais e obtive cada vez , mas acho que não está claro se deve haver uma substituição por ocorrência ou uma substituição por sequência de ocorrências ... @BowPark?
mikeserv
É necessária uma substituição por ocorrência.
BowPark
@BowPark OK, então a primeira abordagem perl ou o awk deve funcionar. Eles não fornecem a saída desejada?
terdon
Funciona, obrigado, mas a terceira linha awkdeve ser print;}' file. Preciso evitar o Perl e usar preferencialmente sed, de qualquer maneira você sugeriu boas alternativas.
22414 BowPark
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
Glenn Jackman
fonte
Sim. Funciona, mas e se eu tiver várias ocorrências?
BowPark
Huh, certo. Corrigido
glenn jackman
1
desculpe nitpick novamente, mas ${cmds}é específico do GNU - a maioria dos outros sedrequer uma \nlinha de e-mail ou uma -epausa entre pe }. Você pode evitar os suportes completamente - e portably - e até mesmo evitar a inserção de um extra de \ncaráter ewline na primeira linha como:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv
Eu testei e parece não ser portátil. Ele imprime uma nova linha extra no início da saída, mas o resultado está correto no GNU.
BowPark
Para remover a nova linha principal: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- no entanto, isso está ficando impossível de manter.
Glenn Jackman
3

Aqui está uma variante da resposta de glenn que funcionará se você tiver várias ocorrências consecutivas (funciona sedapenas com GNU ):

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 isso faz é que ele verifica a linha após a substituição e, se ainda corresponder "line", volta ao :xrótulo (é o que bxfaz) e adiciona outra linha ao buffer e começa a processá-lo.

Joseph R.
fonte
@ mikeserv Seja específico sobre o que você quer dizer. Funcionou para mim.
Joseph R.
@ MikeServ Desculpe, eu realmente não sei do que você está falando. Copiei a linha de código acima de volta para o meu terminal e ela funcionou corretamente.
Joseph R.
1
retraído - isso aparentemente funciona no GNU, sedque leva seu manuseio de etiquetas não POSIX o suficiente para aceitar um espaço como um delimitador para a declaração de etiquetas. Você deve observar, porém, que qualquer outro sedfalhará lá - e falhará N. O GNU sedquebra as diretrizes do POSIX para imprimir o espaço do padrão antes de sair em uma Nna última linha, mas o POSIX deixa claro que, se um Ncomando for lido na última linha, nada deverá ser impresso.
mikeserv
Se você editar o post para especificar o GNU, vou reverter meu voto e excluir esses comentários. Além disso, pode valer a pena aprender sobre o vcomando do GNU, que é interrompido um no outro, sedmas é um no-op nas versões 4 e superiores do GNU.
mikeserv
1
nesse caso vou oferecer uma mais - isso pode ser feito portably como: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv