Exclui uma linha contendo a primeira ocorrência de "pattern1" após a última ocorrência de "pattern2"?

3

Eu tenho um arquivo com esse tipo de conteúdo:

bla bla
pattern2
bla
pattern1
pattern2
bla
bla pattern1 bla
bla
pattern1

Eu gostaria de remover toda a linha em negrito, ou seja, contém a primeira ocorrência de pattern1 após a última correspondência de pattern2 .

Alguém tem uma ideia?

Obrigado!

    
por Davy 09.08.2017 / 20:42

7 respostas

5

Este é um ex one-liner. ( ex é a forma predecessora e com script de vi .)

printf '%s\n' '$?pattern2?/pattern1/d' x | ex file.txt

O x salva e sai. Altere-o para %p se você quiser apenas imprimir o arquivo alterado, mas não salvar as alterações (boas para testes).

$ significa última linha do arquivo; ?pattern2? é um endereço que significa o primeiro resultado de uma busca para pattern2 a partir da posição atual; /pattern1/ é um endereço de pesquisa avançada e d é o comando de exclusão de linha.

Use ex quando precisar de encaminhamento E para trás.

Você pode fazer a mesma coisa interativamente em vi ou Vim:

vim file.txt

Em seguida, digite

:$?pattern2?/pattern1/d

e pressione Enter.

Em seguida, salve e saia com :x Enter.

    
por 10.08.2017 / 00:25
0

Aqui está um método de força bruta. Lê os dados e percorre duas vezes. Encontra a última ocorrência de pattern2 na primeira vez e a primeira ocorrência de pattern1 na segunda vez.

#!/usr/bin/perl

# usage:  perl remove-pattern.pl [file]
use strict;

# reads the contents of the text file completely
# removes end of line character and spurious control-M's
sub load {
   my $file = shift;
   open my $in, "<", $file or die "unable to open $file : $!";
   my @file_contents = <$in>;
   foreach ( @file_contents ) { 
      chomp; 
      s/\cM//g; 
   }
   return @file_contents;
}

#  gets the first file from the command line
#  after the perl script
my $ifile = shift;

# read the text file
my @file_contents = &load($ifile);

# set 2 variables for the index into the array 
my $p2 = -1;
my $p1 = -1;

# loop through the file contents and find the last
# of pattern2 (could go reverse the data and find the 
# first of pattern2
for( my $i = 0;$i < @file_contents; ++$i ) {
   if( $file_contents[$i] =~ /pattern2/) {
      $p2 = $i 
   } 
}

# start at the location of the last of pattern2
# and find the first of pattern1
for( my $i = $p2; $i < @file_contents; ++$i ) {
   if($file_contents[$i] =~ /pattern1/) {
     $p1 = $i ;
     last;
   }
}

# create an output file name
my $ofile = $ifile . ".filtered";

# open the output file for writing
open my $out, ">", $ofile or die "unable to open $ofile : $!"; 

# loop through the file contents and don't print the index if it matches
# p1.  print all others
for( my $i = 0;$i < @file_contents; ++$i ) {
   print $out "$file_contents[$i]\n" if ($i != $p1);
}


--- data.txt  ---
bla bla
pattern2
bla
pattern1
pattern2
bla
bla pattern1 bla
bla
pattern1

Se o script perl acima fosse chamado de 'remove-pattern.pl', ele seria executado com o seguinte comando, dado o arquivo de entrada data.txt.     % > perl remove-pattern.pl data.txt

Arquivo de saída resultante 'data.txt filtrado'

--- data.txt.filtered ---
bla bla
pattern2
bla
pattern1
pattern2
bla
bla
pattern1
    
por 09.08.2017 / 21:38
0

Para encontrar o número da linha dessa linha:

lineno=$( nl file | tac | awk '/pattern1/ {last = $1} /pattern2/ {print last; exit}' )

Usando nl para adicionar números de linha ao arquivo, tac para reverter as linhas,
e awk para imprimir o número da linha do último "padrão1" antes do primeiro "padrão2".

E, em seguida, para excluir essa linha:

sed -i "${lineno}d" file
    
por 09.08.2017 / 21:38
0

Não tenho computador aqui para testar, mas isso deve funcionar com o gnu sed :

sed 'H;1h;$!d;g;s/.*pattern1/@@@/;s/\n[^\n]*pattern2[^\n]*//;H;g;s/\(.*pattern1\).*@@@//'

Em vez de @@@ , use qualquer sequência de caracteres que não seja parte do arquivo.

    
por 10.08.2017 / 00:17
0

Se você quisesse fazer apenas uma passagem no arquivo e minimizar o número de linhas para manter na memória, você poderia usar awk com uma abordagem de máquina de estado. Aqueles que não oferecem as soluções mais curtas, mas são fáceis de criar e ler / manter. Você pode substituir os nomes de estado por números para torná-lo (possivelmente) mais eficiente.

PATTERN1=pattern1 PATTERN2=pattern2 awk '
  BEGIN {
    p1 = ENVIRON["PATTERN1"]
    p2 = ENVIRON["PATTERN2"]
    state = "init"
  }
  state == "init" {
    if ($0 ~ p2) state = "p2_found"
    print
    next
  }
  state == "p2_found" {
    if ($0 ~ p1) {
      state = "p1_found"
      p1_line = $0
      printf "%s", hold
      hold = ""
    } else if ($0 ~ p2) {
      # we can print the text held since the last p2
      printf "%s", hold
      hold = $0 RS
    } else hold = hold $0 RS
    next
  }
  state == "p1_found" {
    if ($0 ~ p2) {
      state = "p2_found"
      # the line that matched p1 is not discarded
      printf "%s\n%s", p1_line, hold;
      hold = ""
    }
    hold = hold $0 RS
  }
  END {
    # here we are not printing p1_line which is how it is discarded
    printf "%s", hold
  }'

(suponho que não haja linhas que correspondam a pattern1 e pattern2 ).

    
por 10.08.2017 / 10:39
0

Podemos realizar a verificação usando sed , embora o código abaixo seja escrito para GNU sed , onde mantemos o último intervalo completo /pat2/../pat1/ no espaço de armazenamento. E, então, mantemos um envelope de expansão de fronteira a partir da linha ao lado da última linha do intervalo armazenado em espera e avançando em direção ao eof.

sed -e '
   /pattern2/,/pattern1/!b
   H;/pattern2/h;/pattern1/!{$!d;g;q;}
   ${g;bc;}
   N;s/.*\n//
   :a
      $bd;N
   /pattern2/!ba
   :b
      $bd;N
   /pattern2.*\n.*pattern1/!bb
   x;$!{n;/pattern2/bb;ba;}
   G
   :c;s/\(.*\)\n.*//;q
   :d;x;s/\(.*\)\n.*//;G
' input,txt

Neste método, nós sorveremos todo o arquivo no espaço padrão, então, usando o poder das expressões regulares, localize e determine o último / pattern1 / line em um intervalo e exclua-o.

sed -Ee '
   $!{N;H;s/.*//;x;D;}
   /pattern2/!q;/pattern1/!q;/pattern2.*\n.*pattern1/!q
   h;s/((.*\n)?[^\n]*pattern2[^\n]*)\n(.*pattern1.*)//
   s/^//;tdummy
   :dummy
   s/\n[^\n]*pattern1[^\n]*\n/\n/;ta
   s/^[^\n]*pattern1[^\n]*\n//;ta
   s/\n[^\n]*pattern1[^\n]*$//;ta
   s/^[^\n]*pattern1[^\n]*$//
   :a;x;s/((.*\n)?[^\n]*pattern2[^\n]*)\n.*pattern1.*//;G
' input.file
    
por 12.08.2017 / 11:57
0

Sed:

n=$(sed -ne '/pattern2/,/pattern1/{/pattern1/=;}' yourfile | tail -n 1)
sed -i'' -e "${n}d" yourfile

Sed: baseado em 1 passe

# invoke GNU sed with extended RE(-E), slurp mode(-z), in-place editing(-i) options
sed -i -Eze '
   h;s/(.*pattern2[^\n]*)\n.*//p;   # traverse till the last pat2 line and print it
   g;s/.*pattern2[^\n]*(\n.*)//;    # remove till the last pat2 line
   s/\n[^\n]*pattern1[^\n]*//;        # now look for the 1st occurrence of pat1
   ;                                  # clip that line, & print what remains
' inp

Trabalhando:

  • Determine o número da linha de / pattern1 /, mas isso deve estar apenas no intervalo adequado, ou seja, /pattern2/,/pattern1/ para que seja considerado.
  • Pegue o último desses números.
  • Na segunda passagem, forneça o número da linha determinado acima e exclua essa linha.

Perl:

     |----A---|   |----B----|--------C--------|D|----------E---------|-F-|
perl -0777pi -e 's/.*pattern2(?:(?!pattern1).)*\K(?-s:\n.*pattern1.*$)//ms' yourfile
  • A : invocar Perl em% modoslurp ( -0777 ) + line-by-line leitura + autoprint ( -p ) ativado, in-place edição ( -i )
  • B+C : Atravessar até a última ocorrência do pattern2 e então diminuir a velocidade e ir de forma constante e arrumada para fora do pattern1
  • D : Quando você chegar aqui, esse é o último pit stop em que pattern1 não foi visto após o último pattern2, marque-o com \K . o que significa que isto não aparecerá na parte consumida da entrada e sim na parte correspondente.
  • E : Desativamos o modificador de correspondência de padrão cloistered /s , o que significa que, nessa parte da correspondência, o . não corresponde mais à nova linha, o que significa que não podemos ultrapassar as linhas. Nós apenas combinamos com toda a linha que tem o pattern1 e removemos isso. O que resta no espaço do padrão é autoprintado.
  • -F : invocamos os modificadores s/// with /s e /m . Vamos desligar seletivamente o modificador /s dentro do regex para adequar às nossas necessidades.
por 10.08.2017 / 08:56

Tags