Use sed para imprimir desde a primeira linha até a linha que contém a última ocorrência de um padrão?

5

Sei que os seguintes comandos serão impressos até a primeira ocorrência de um determinado padrão, mas não incluirão as ocorrências posteriores:

sed -n '1,/<pattern>/p' <file>

sed '/<pattern>/q' <file>

Por exemplo, digamos que eu tenha um arquivo com as seguintes linhas:

this is a cow   
this is a goat  
this is a some fish  
this is a fishie  
this is a fish  
this is a lion  
this is a cat

Agora as saídas:

$ sed '/fish/q' file  

this is a cow  
this is a goat  
this is a some fish 

$ sed -n '1,/fish/p' file  

this is a cow  
this is a goat  
this is a some fish 

Eu quero a saída começando da primeira linha até a linha que contém a última ocorrência de fish , ou seja, minha saída desejada é:

this is a cow   
this is a goat  
this is a some fish  
this is a fishie  
this is a fish 

Como fazer isso usando sed ?

    
por heemayl 02.12.2014 / 20:42

4 respostas

4

Tente isto:

$ tac infile | sed -n '/fish/,$p' |tac

No caso geral, se você executar abaixo do comando sed, você obterá todas as linhas do primeiro padrão correspondente até o final do arquivo de entrada.

$ sed -n '/fish/,$p' file

this is a some fish
this is a fishie
this is a fish
this is a lion
this is a cat

Portanto, minha solução é: Se executarmos o comando tac no arquivo de entrada, seu último padrão correspondente será alterado para o primeiro padrão. veja o resultado de tac infile :

$ tac infile

this is a cat
this is a lion
this is a fish
this is a fishie
this is a some fish
this is a goat
this is a cow

O comando tac é igual ao comando cat , enquanto tac imprime os arquivos na ordem inversa.

Agora, se executarmos nosso primeiro comando sed, você obterá todas as linhas do primeiro padrão correspondente ao final do arquivo de entrada. como:

$ tac infile | sed -n '/fish/,$p'
this is a fish
this is a fishie
this is a some fish
this is a goat
this is a cow

Ok, terminado. Nós só precisamos executar o comando tac novamente para reverter as linhas para a ordem original:

$ tac infile | sed -n '/fish/,$p' |tac
this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish

Feito!

    
por 03.12.2014 / 21:37
3

Isso é bastante simples - basta uma pequena coordenação entre os dois buffers de sed . Por exemplo:

sed -n 'H;/fish/!d;$!n;x;p;G
' <<\INFILE
this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish
this is a lion
this is a cat
INFILE

Esse comando anexa todas as linhas a H old space após um caractere \n ewline inserido. Qualquer linha que não ! não corresponde a /fish/ é imediatamente a partir de então d da saída. O que nos deixa com apenas /fish/ linhas. Portanto, a linha é sobrescrita com a linha de entrada n ext. Então padrão e h espaços antigos são trocados - nós fizemos apenas H old a linha depois de tudo. Agora o espaço de padrão é H old space e vice-versa. Então, apenas p rint foi salvo da última vez que uma linha /fish/ foi correspondida.

Isso só pode levar até a última ocorrência de uma correspondência, porque é apenas p rints quando encontra uma correspondência - e armazena as linhas intermediárias nesse intervalo. Ainda assim, ele armazena apenas o mínimo necessário entre as correspondências - cada vez que os buffers são e x alterados, eles são atualizados. Aqui está sua saída:

this is a cow   
this is a goat  
this is a some fish  
this is a fishie  
this is a fish

Muito obrigado a don cristi por me mostrar que às vezes eu pulei um peixe. Agora, ele certifica-se de empurrar o buffer para ambas as extremidades cada vez que ele é atualizado - e cada vez para sobrescrever o espaço padrão atual antes de excluí-lo - no caso. Ele funcionará com um peixe na primeira linha ou no último, e qualquer linha no meio, tanto quanto eu puder dizer.

Outra coisa que eu estava fazendo era puxar a linha n ext na última linha - que é um grande sed no-no. Obrigado novamente por me ajudar com isso também.

Um exemplo mais completo:

sed 'x;/./G;//!x;/fish/p;//s/.*//;x;d'

Isso lida com alguns problemas em torno do outro melhor, espero. Os espaços padrão / h old são e x alterados a cada ciclo - e isso é para evitar obter linhas em branco extras como resultado da edição s/// ubstitution no final do comando. Portanto, os buffers são trocados e, se o buffer de manutenção não estiver vazio, a linha atual é anexada nesse buffer após um caractere \n ewline - que é onde as linhas extras viriam de outra forma. Senão os buffers são apenas trocados de volta e o h old space permanece vazio para o ciclo atual. Por mais que eu saiba, este comando preserva todas as linhas em branco e tudo mais - mas simplesmente pára a impressão na última partida.

Algumas das dificuldades que tive com isso são comumente associadas com h old space - a única maneira de usá-lo efetivamente é ficar aquém do ciclo de linha para comparar as linhas antigas com as mais antigas. Minha preferência usual é por um loop N;P;D . Você pode fazer isso usando algo como essas ...

sed -ne :n -e '/fish/!N;//p;//!bn'

sed anexa continuamente a linha de entrada N ext ao espaço padrão e b ranchos de volta ao rótulo :n para tentar novamente se peixe não corresponder a nenhum dos linhas que construiu até agora. Apenas p rints linhas - ou sequências de linha - que correspondem a fish antes de descarregar o conteúdo no final do ciclo de linha e começar de novo com um novo buffer.

Eu propositadamente não testei a última linha aqui - se a última linha coincidir será impressa, senão - e com -n isto é válido mesmo para o GNU sed - o laço apenas terminará com o arquivo todo sua própria última linha ou não.

    
por 03.12.2014 / 22:51
0

Você também pode usar o awk, que pode ser menor que Sed:

awk ' /^fish/ { print $0 }' filename.txt

Algumas pessoas podem escrever isso:

awk ' /^fish/ { print $1 $2 $3 $4 $5 }' filename.txt

O $n representa uma coluna. O atalho $0 representa a linha inteira.

    
por 02.12.2014 / 21:47
0

A questão não está muito clara, mas depois de algumas iterações de edição, acredito que você deve usar pcregrep para essa tarefa. Por exemplo, se a entrada for

this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish
this is a lion
this is a cat
this is a fisherman
this is a dog

e desejo de saída

this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish

então

pcregrep -M '(.|\n)*fish.*(?=(.|\n)*fish.*)'

fará o trabalho.

    
por 02.12.2014 / 23:14