Gostaria de inserir uma nova string na última linha começando com algum texto usando sed,

Gostaria de inserir uma nova string na última linha começando com algum texto usando sed,

Pesquise no arquivo a última linha com uma determinada String para adicionar uma linha após essa correspondência. Exemplo:

Entrada:

Apple
Banana
Apple
Orange
Apple
Grapefruit

Saída:

Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Grapefruit

Responder1

Com o GNU sedvocê pode fazer isso:

sed ':a;N;$! ba;s/.*\nApple/&\nYour appended line/'

O padrão :a;N;$! baanexa linhas do arquivo ao buffer Nenquanto o final do arquivo ( $endereço) não é ( !) alcançado (pular para :acom ba). Portanto, se você sair do loop, todas as linhas estarão no buffer e o padrão de pesquisa .*\nApplecorresponderá ao arquivo inteiro até a última linha começando com Apple. Na &string de substituição, insira toda a correspondência e anexe seu texto.

Uma solução portátil que funcionasse sedtambém em outras versões seria

sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'

Aqui o script é dividido nos rótulos e em vez de usar \nna string de substituição (que não é garantido que funcione com s não-GNU sed) nos referimos àquele do padrão cercado \(\)pela \1referência.

Responder2

Eu vi esse exemploaqui

sed -i -e "\$aTEXTTOEND" <filename>

Informação:

i: edit files in place    
e: add the script to the commands to be executed    
$: sed address location
a: append command
TEXTTOEND: text to append to end of file

Responder3

Presumo que sua entrada de exemplo seja um arquivo chamado fruits.txt.


Se você deseja inserir uma linha após a última ocorrência de “Apple”, isso pode ser feito sedassim:

sed -rz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'

A -ropção permite expressões regulares estendidas.
A -zopção diz sedpara usar caracteres NUL (código ASCII 0) como separadores de linha em vez dos caracteres normais de nova linha. Dessa forma, todo o arquivo de texto será processado de uma só vez, em vez de linha por linha.

O sedcomando s/PATTERN/REPLACEMENT/procura a -rexpressão regular (estendida, por causa de) PATTERNe a substitui pela REPLACEMENTstring avaliada.

A expressão regular (^(.*\n)?Apple)(\n|$)corresponde a qualquer número de linhas desde o início ( ^) até a última ocorrência Appleseguida por uma quebra de linha ( \n) ou o final do arquivo ( $).

Agora toda esta partida é substituída por \1\ninserted line\n, onde \1representa o conteúdo original do primeiro grupo de partidas, ou seja, tudo até Apple. Então, na verdade, ele é inserido inserted lineprecedido e seguido por uma quebra de linha ( \n) logo após a última ocorrência de "Apple".

Isso também funciona se a linha “Apple” for a primeira, a última ou a única linha do arquivo.

Arquivo de entrada de exemplo (adicionada uma linha ao seu exemplo, para deixar o comportamento claro):

Apple
Banana
Apple
Orange
Apple
Cherry

Exemplo de saída:

Apple
Banana
Apple
Orange
Apple
inserted line
Cherry

Se você estiver satisfeito com o resultado e quiser editar fruits.txtno local, em vez de apenas gerar a modificação, você pode adicionar a -iopção:

sed -irz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'

Se você deseja apenas anexar uma linha de texto ao final do arquivo, essa sednão é a ferramenta ideal.

Você pode simplesmente usar o redirecionamento de saída anexado do Bash >>:

echo "I am new string replaced at last apple" >> fruits.txt

O >>irá pegar a saída padrão do comando à sua esquerda (o echocomando aqui simplesmente imprime seus argumentos na saída padrão) e anexa-o ao arquivo especificado à direita. Se o arquivo ainda não existir, ele será criado.

Responder4

Nesta resposta:

  1. sed+tac (reversão de entrada dupla)
  2. AWK com entrada dupla
  3. Perl com entrada dupla
  4. Perl alternativo com reversão de entrada dupla

sed + tac

Esta abordagem usa 3 processos, primeiro revertendo o arquivo, usando sed para encontrar a primeira correspondência e inserindo o texto desejado após a primeira correspondência, e revertendo o texto novamente.

$ tac input.txt | sed '0,/Apple/{s/Apple/NEW THING\n&/}' | tac 
Apple
Banana
Apple
Orange
Apple
NEW THING
something else
something other entirely
blah

A ideia básica é a mesma da versão alternativa do Perl escrita abaixo: a primeira correspondência na lista reversa de linhas é a última correspondência na lista original. A vantagem desta abordagem é a simplicidade e a legibilidade para o usuário.

Essa abordagem funcionará bem para arquivos pequenos, mas será complicada para arquivos grandes e provavelmente levará um tempo considerável para processar uma grande quantidade de linhas.

AWK

Awk pode ser um pouco mais amigável para o que você está tentando fazer:

$ awk -v n="AFTER LAST APPLE" 'NR==FNR&&/Apple/{l=NR};NR!=FNR{if(FNR==l){print $0;print n}else print}' input.txt input.t>
Apple
Banana
Apple
Orange
Apple
AFTER LAST APPLE
something else
something other entirely
blah

A idéia básica aqui é fornecer seu arquivo de entrada para awk duas vezes - primeiro para descobrir onde está a última Apple e, segundo, para inserir o texto quando passarmos a posição da última "maçã" que encontramos na primeira iteração.

A chave para fazer este trabalho é avaliar NR(que continua contando todas as linhas processadas, totais) e FNR(número da linha no arquivo atual) variáveis. Quando esses dois arquivos são desiguais, sabemos que estamos processando o segundo arquivo, portanto sabemos que fizemos nossa primeira tentativa de encontrar a localização da última ocorrência de Apple.

Como estamos lendo o arquivo duas vezes, o desempenho dependerá do tamanho do arquivo, mas é melhor que a combinação sed + tac, pois não precisamos reverter o arquivo duas vezes.

Perl

perl -sne '$t+=1;$l=$. if /Apple/ and $.==$t;
          if($t!=$.){if($.==$l){print $_ . $n . "\n";}else{print $_}};
          close ARGV if eof;' -- -n="AFTER LAST APPLE" input.txt input.txt

A solução Perl segue a mesma ideia da AWK. Passamos a entrada duas vezes e usamos a primeira vez que o arquivo é passado para descobrir onde está a última ocorrência de Apple e armazenar seu número de linha na $lvariável. O que é diferente do awk aqui é que não há FNRvariável em perl, portanto temos que usar $t+=1e close ARGV if eofpara esse propósito.

O que também é diferente aqui é que com -sswitch perl permite passar argumentos. Neste caso a string que queremos inserir é passada via -nswitch.

Perl alternativo

Aqui está outra maneira de fazer isso em Perl. A ideia é simplesmente ler o arquivo em um array de linhas e reverter esse array. A primeira correspondência na matriz invertida é a última na lista original de linhas. Assim, podemos inserir um texto desejado antes dessa linha, e inverter novamente a lista:

$ perl -le '@a1=<>;END{do{push @a2,"NEW LINE\n" and $f=1 if $_ =~ /Apple/ and !$f; push @a2,$_} for reverse(@a1); print reverse(@a2)}' input.txt      
Apple
Banana
Apple
Orange
Apple
NEW LINE
something else
something other entirely
blah

informação relacionada