Apagar a linha Nth de cada linha que corresponde a um padrão

4

Eu tenho vários arquivos como file1 , file2 ... etc no mesmo diretório e cada arquivo pode conter várias linhas correspondentes a PATTERN .
Gostaria de excluir a linha N th de cada linha correspondente a PATTERN , por exemplo com N = 3 e file1 conteúdo como

1 no match
2 PATTERN
3 same PATTERN
4 no match here
5 no match here either
6 another PATTERN
7 again, no match
8 no
9 last line

o resultado esperado é

1 no match
2 PATTERN
3 same PATTERN
4 no match here
7 again, no match
8 no

Editar os arquivos no local é um bônus, não um requisito (embora haja pelo menos uma ferramenta gnu que eu saiba que possa editá-los todos de uma só vez ...)

Uma pergunta semelhante foi feita aqui , mas esse é um caso particular (há apenas um padrão de correspondência de linha em cada arquivo e as soluções lá só funcionariam com várias linhas padrão de correspondência se elas estiverem separadas por pelo menos N +1 linhas não correspondentes).

    
por don_crissti 02.05.2016 / 20:27

4 respostas

6

Você pode usar awk para isso, assim eu acredito:

awk -vN=3 '/PATTERN/ {skips[FNR+N]=1;} {if(!(FNR in skips)) print;}' <file>

para que, sempre que atingirmos PATTERN , registremos a linha que é N daqui e só imprimamos as linhas que não marcamos para pular.

com o gawk você pode usar -i inplace para fazer isso no lugar

Como você observou, isso não lida com vários arquivos. É claro, você pode agrupar com um loop for para iterar todos os arquivos, mas se não houver tempo suficiente para tornar a linha de comando muito longa, você também poderá fazer isso:

 awk -vN=3 '{if(FNR==1) split("", skips, ":");} /PATTERN/ {skips[FNR+N]=1;} {if(!(FNR in skips)) print;}' *

onde redefinimos skips para uma matriz vazia sempre que FNR atinge 1, portanto, o início de cada arquivo.
Com gnu awk você poderia escrever como:

gawk -i inplace 'FNR==1{delete nr};/PATTERN/{nr[FNR+3]++};!(FNR in nr)' file*
    
por 02.05.2016 / 20:51
2

Eu gosto de um mecanismo de 2 passagens para que possamos usar sed -i :

for file in file1 ...
do sed -i "$file" -e "$(awk <"$file" -v N=3 '/PATTERN/{ print (NR+N) "d" }')"
done
    
por 02.05.2016 / 20:52
2
for f in file1 file2 file...; do
  sed -i -f <(grep -n PATTERN "$f" | while IFS=: read line rest; do printf "%dd; " $((line+3)); done) "$f"
done

Para dividir isso:

  1. Faz o loop sobre o arquivo file1 file2 ...

  2. crie uma expressão sed dentro da substituição do processo para, eventualmente, executar o arquivo.

  3. grep gera números de linha correspondentes a PATTERN no arquivo (junto com a linha real correspondente).

Exemplo de saída:

2:2 PATTERN
3:3 same PATTERN
6:6 another PATTERN
  1. o loop while retira o número da linha, descartando a linha correspondente e enviando-o para printf, incrementado em 3

  2. printf imprime o número da linha de destino, seguido pelo comando sed d delete e um ponto-e-vírgula de separação.

Exemplo de saída (como entrada para sed ):

5d; 6d; 9d;

Este método permite uma boa quantidade de flexibilidade; você pode definir N=3 e usar $((line+N)) como o argumento printf.

Para explicar a edição no local, presumo que um sed que suporte a edição -i "no local".

    
por 02.05.2016 / 21:41
2

Este caso de uso apenas implora para usar ex .

Infelizmente, como excluir a terceira linha após uma determinada linha pode excluir uma linha contendo PATTERN e, assim, fazer com que a exclusão associada a essa linha seja ignorada (ou pior, excluir a linha incorreta), você precisa inverter o arquivo usando por exemplo tac primeiro. Então você pode deletar a terceira linha antes de cada instância do PATTERN, e inverter o arquivo mais uma vez:

for f in *.txt; do printf %s\n '%!tac' 'g/PATTERN/-3d' '%!tac' x | ex "$f"; done

Se você tem tac disponível, acho que esta é a solução mais limpa.

Para uma solução totalmente compatível com POSIX , fazendo uso da minha resposta para:

Você pode fazer assim:

for f in *.txt; do printf %s\n '%!sed -n '\''1h;1\!{x;H;};${g;p;}'\' 'g/PATTERN/-3d' '%!sed -n '\''1h;1\!{x;H;};${g;p;}'\' x | ex "$f"; done

Não é muito legível, mas funcional.

    
por 03.05.2016 / 01:32