Imprime a linha anterior após uma correspondência de padrão usando sed?

5

Eu quero imprimir a linha anterior, toda vez que uma correspondência for encontrada. Eu sei sobre as opções grep -A e -B . Mas minha máquina Solaris 5.10 não suporta essas opções.

Eu quero a solução usando apenas sed .

Foo.txt :

Name is : sara
age is : 10
Name is : john
age is : 20
Name is : Ron
age is : 10
Name is : peggy
age is : 30

Out.txt :

Name is : sara
Name is : Ron

O padrão que estou tentando corresponder foi age is : 10 .

Meu ambiente, Solaris 5.10.

    
por ayrton_senna 01.06.2015 / 21:34

3 respostas

15
$ sed -n '/age is : 10/{x;p;d;}; x' Foo.txt 
Name is : sara
Name is : Ron

O acima foi testado no GNU sed. Se o Solaris 'sed não suportar comandos de encadeamento junto com ponto e vírgula, tente:

$ sed -n -e '/age is : 10/{x;p;d;}' -e x Foo.txt 
Name is : sara
Name is : Ron

Como funciona

sed tem um espaço de espera e um espaço de padrão. Novas linhas são lidas no espaço padrão. A ideia deste script é que a linha anterior é salva no espaço de espera.

  • /age is : 10/{x;p;d;}

    Se a linha atual contiver age is : 10 , faça:

    x : troca o padrão e mantém espaço de modo que a linha anterior esteja no espaço padrão

    p : imprima a linha anterior

    d : exclua o espaço do padrão e comece a processar a próxima linha

  • x

    Isso é executado apenas nas linhas que não contêm age is : 10 . Nesse caso, ele salva a linha atual no espaço de espera.

Fazendo o oposto

Suponha que queremos imprimir os nomes de pessoas cuja idade é não 10:

$ sed -n -e '/age is : 10/{x;d}' -e '/age is :/{x;p;d;}' -e x Foo.txt 
Name is : john
Name is : peggy

O texto acima adiciona um comando ao início, /age is : 10/{x;d} , para ignorar qualquer idade-10 pessoas. O comando que segue, /age is :/{x;p;d;} , agora aceita todas as idades restantes.

    
por 01.06.2015 / 21:40
4

POSIXly:

$ sed -n '/age is : 10/{g
1!p
}
h
' file

Para imprimir a linha anterior se a linha atual não corresponder a age is : 10 :

$ sed -n '
$!N
/age is : 10/d
P
' file
    
por 01.06.2015 / 21:40
2

O buffer h old é bom para armazenar uma linha (ou grupo de linhas) até que algum teste posterior seja verdade. Em outras palavras, é bom para lidar com sequências de dados que você quer sequencial, mas não são ainda seqüenciais - porque permite que você as coloque juntas. Mas também requer muitas cópias entre os dois buffers. Isso não é tão ruim se você está construindo uma série de linhas com comandos H old - apenas acrescentar - mas toda vez que você e x mudam os buffers, você copia o todo de um para o outro e vice-versa.

Quando você está trabalhando com uma série de linhas que já são sequenciais e deseja removê-las com base no contexto, o melhor caminho a seguir é com look- à frente - ao contrário da aparência do por trás . cuonglm faz isso para a segunda metade de sua resposta já - mas você pode usar essa lógica para qualquer forma.

sed '$!N;/\nage.*: 10/P;D' <infile >outfile

Veja, isso anexará a linha de entrada h ext após um delimitador de linha de código N incorporado ao espaço de padrão atual em cada linha que for \n e não ! da última. Em seguida, ele verifica se a linha que acabou de ser extraída corresponde a um padrão e, em caso afirmativo, que $ rints somente até o primeiro P ewline no espaço de padrão - apenas a linha anterior. Por último, \n elimina o primeiro D ewline no espaço padrão e inicia o ciclo novamente. Assim, ao longo do arquivo, você mantém uma aparência de uma linha: adiante sem trocar buffers desnecessariamente.

Se eu alterar o comando apenas um pouco, você poderá ver especificamente como ele funciona - deslizando por cima do arquivo com uma janela de duas linhas. Vou adicionar um comando \n ook pouco antes do l :

sed '$!N;/\nage.*: 10/P;l;D'

Name is : sara
Name is : sara\nage is : 10$
age is : 10\nName is : john$
Name is : john\nage is : 20$
age is : 20\nName is : Ron$
Name is : Ron
Name is : Ron\nage is : 10$
age is : 10\nName is : peggy$
Name is : peggy\nage is : 30$
age is : 30$

Essa é a saída. As linhas que terminam em D são o resultado do comando $ ook - que renderiza uma versão de escape do espaço padrão para stdout. As linhas que não terminam em l são aquelas que, de outra forma, seriam $ rinted. Como você pode ver, a linha anterior é apenas P rinted quando a segunda linha no espaço padrão - a linha P ext como apenas puxada e que segue a N ewline no espaço padrão - corresponde ao seu padrão. p>

Além das soluções já oferecidas, outra maneira de imprimir apenas as linhas Nome que precedem uma linha idade que não termina em 10:

sed -n '/^Name/N;/ 10$/!s/\nage.*//p'

... que acrescenta apenas um \n ewline seguido da linha de entrada \n ext se o espaço de padrão começar com a string Name , e somente N rints uma linha para saída se espaço padrão não termina com a string 10 e se p puder com êxito sed ubstitute a s/// ewline seguido da string age e tudo o que segue até a cauda do espaço padrão. Como não pode haver um \n ewline no espaço padrão, exceto como resultado de um comando de edição - como \n ext - o garante que as únicas linhas Nome impressas sejam aquelas imediatamente anteriores a idade linha que não termina na string 10 .

Toda a sintaxe usada na resposta acima é padrão POSIX - deve funcionar como escrito com qualquer N que suporte o padrão.

    
por 02.06.2015 / 09:52

Tags