Exclua o intervalo de linhas acima do padrão com sed (ou awk)


28

Eu tenho o seguinte código que removerá linhas com o padrão bananae 2 linhas depois dele:

sed '/banana/I,+2 d' file

Por enquanto, tudo bem! Mas preciso remover duas linhas antes banana , mas não consigo obtê-lo com um "sinal de menos" ou o que for (semelhante ao que grep -v -B2 banana filedeve fazer, mas não faz):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

11
A mais fácil é para carregar todos os dados em uma matriz, ignorar as linhas indesejadas, em seguida, o que permanece saída: awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. Isso não é eficiente, portanto, é apenas uma dica, não uma solução.
manatwork

6
Apenas faça tac file | sed ... | tac. : P
angus

@angus eu não pensei nisso;)
Teresa e Junior

11
você poderia ter feito sed '/banana/,+2d' file que também funcionará
Akaks

11
Se você está aberto a usar o awk, é bem simples: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein como esse é um comentário e não uma resposta (já existem outras respostas), não entrarei em muitos detalhes, mas o ponto crucial é que você sempre tem o anteriores dois registros em prev [0] e prev [1], o "mais frescos" dependendo de qual iteração mas sempre prev[idx], então quando você imprimir, você imprime em !idxseguida idxordem. Independentemente disso, alterne idxe insira o registro atual prev[idx].
Luv2code

Respostas:


22

O Sed não volta atrás: uma vez processada uma linha, está pronta. Portanto, “encontre uma linha e imprima as N linhas anteriores” não funcionará como está, ao contrário de “encontre uma linha e imprima as próximas N linhas”, que são fáceis de enxertar.

Se o arquivo não for muito longo, já que você parece estar bem com as extensões GNU, você pode usar tacpara reverter as linhas do arquivo.

tac | sed '/banana/I,+2 d' | tac

Outro ângulo de ataque é manter uma janela deslizante em uma ferramenta como o awk. Adaptação de Existe alguma alternativa aos comutadores -A -B -C do grep (para imprimir algumas linhas antes e depois)? (aviso: minimamente testado):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Uso: /path/to/script -v pattern='banana' -v before=2


2
sedtambém pode fazer janelas deslizantes, mas o script resultante geralmente é tão ilegível que é mais fácil usar apenas awk.
Jw013

@ Gilles .. O awkscript não está certo; como está, imprime linhas em branco e perde as últimas linhas. Isso parece corrigi-lo, mas pode não ser ideal ou correto: if (NR-before in h) { print...; delete...; }... e na ENDseção: for (i in h) print h[i]... Além disso, o script awk imprime a linha correspondente, mas a tac/secversão não; mas a pergunta é um pouco ambígua nisso .. O script awk "original", para o qual você forneceu um link, funciona bem .. eu gosto ... não tenho certeza de como o 'mod' acima afeta a impressão depois linhas ...
Peter.O

@ Peter.O Obrigado, o script awk deve ser melhor agora. E levei menos de 6 a 8 anos!
Gilles 'SO- stop be evil'

19

Isso é bem fácil com ex ou vim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

A expressão diz: para cada linha que contém banana no intervalo da linha atual -2 à linha atual, exclua.

O legal é que o intervalo também pode conter pesquisas para trás e para a frente, por exemplo, isso excluirá todas as seções do arquivo começando com uma linha contendo maçã e terminando com uma linha contendo laranja e contendo uma linha com banana:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

Usando a "janela deslizante" em perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

Você pode fazer isso de maneira bastante simples com sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Não sei por que alguém diria o contrário, mas, para encontrar uma linha e imprimir as linhas anteriores, sed incorpora a Pprimitiva integrada da rint, que grava apenas até o primeiro \ncaractere da linha de ewline no espaço do padrão. A Dprimitiva elete complementar remove o mesmo segmento do espaço do padrão antes de reciclar recursivamente o script com o que resta. E, para finalizar , existe uma primitiva para anexar a Nlinha de entrada ext ao espaço do padrão, após um \ncaractere de linha de linha inserido .

Para que uma linha de sedseja tudo o que você precisa. Você apenas substitui o que matchquer que seja o seu regexp e fica com o ouro. Essa deve ser uma solução muito rápida também.

Observe também que ele contará corretamente um matchimediatamente anterior a outro matchcomo um acionador para silenciar a saída das duas linhas anteriores e também silenciar sua impressão:


1
7match
8
11match

Para funcionar para um número arbitrário de linhas, tudo o que você precisa fazer é obter uma vantagem.

Tão:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... exclui as 5 linhas que precedem qualquer correspondência.


1

Usando man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.