Imprime dados entre duas linhas (somente se existir “end range”) a partir de um arquivo de texto

0

Eu preciso analisar um arquivo e estou procurando imprimir um segmento de dados entre duas linhas específicas. De um "início do intervalo" até "final do intervalo", mas apenas se o "fim do intervalo" estiver presente.

Se o arquivo de origem for:

[This is the start] of some data 
this is information
this is more information
This is does not contain the ending required

[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

Deve imprimir:

[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

Usando o grep, consegui encontrar os dados de que preciso e imprimir para cima, mas apenas por um número fixo de linhas.

Dado que o número de linhas de dados não é constante, existe uma maneira de usar grep ou sed, para trabalhar a partir da linha de fim para encontrar a próxima ocorrência de uma determinada string e capturar o intervalo específico que eu quero?

O "início do intervalo" do segmento de dados deve ser impresso junto com qualquer dado entre o "início do intervalo" e o "fim do intervalo", e a correspondência "fim do intervalo" é o que determina se o intervalo inteiro de linhas deve ser impresso em tudo. Se um intervalo (segmento de dados) não tiver o final especificado, ele não deverá ser impresso. Se vários segmentos tiverem um ponto final, todos os segmentos contendo um final deverão ser impressos. Não existe nenhum caso em que o arquivo de entrada terá um fim sem uma partida ou vários fins para uma única partida.

As linhas de impressão entre (e incluindo) dois padrões não resolvem meu problema, pois ele começa a imprimir na primeira linha correspondida e mantém a impressão até que o primeiro segmento final seja encontrado. Preciso imprimir apenas os segmentos que contêm a instrução final especificada.

    
por Erudaki 17.07.2018 / 21:57

5 respostas

5

Usando sed :

$ sed -n '/This is the start/{h;d;}; H; /This is the ending/{x;p;}' file
[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

Script sed anotado:

/This is the start/{    # We have found a start
    h;                  # Overwrite the hold space with it
    d;                  # Delete from pattern space, start next cycle
};

H;                      # Append all other lines to the hold space

/This is the ending/{   # We have found an ending
    x;                  # Swap pattern space with hold space
    p;                  # Print pattern space
};

O que o script faz é salvar todas as linhas no "espaço de espera" (um buffer de uso geral em sed ), mas assim que encontramos uma "linha de partida", redefinimos esse espaço. Quando uma "linha final" é encontrada, os dados salvos são impressos.

Isso quebra se uma "linha final" for encontrada antes de uma "linha inicial" e possivelmente também se duas "linhas finais" forem encontradas sem nenhuma "linha inicial" entre elas.

Um programa awk que executa o mesmo procedimento que o programa sed acima:

$ awk '/This is the start/  { hold = $0; next }
                            { hold = hold ORS $0 }
       /This is the ending/ { print hold }' file

(saída idêntica como acima)

    
por 17.07.2018 / 22:07
0

Com vários padrões START e END , você pode fazer assim:

sed 'H;/START/h;/END/!d;x;/START/!d' infile

Isso acumulará linhas incondicionalmente no buffer H old, sobrescrevendo-o via h sempre que uma linha START for encontrada (ou seja, mantendo apenas os dados da linha START mais recente), d eletria o espaço padrão se não contiver uma linha END (o ciclo reinicia aqui), caso contrário, e x alterando os buffers e novamente, d eliminando o espaço padrão, desta vez se não contiver %código%. Tudo o que resta é autoprinted.

    
por 17.07.2018 / 23:51
0

Use tac para inverter a ordem das linhas

Se você usar tac para reverter o arquivo - para imprimir primeiro a última linha, e assim por diante -, poderá extrair a região do padrão final para o padrão inicial. Em seguida, use tac novamente para imprimir as linhas de saída em ordem direta.

tac file.txt | awk '/^\[This is the ending I was looking for]/,/^\[This is the start]/ { print $0 }' | tac

O mesmo código, formatado para se ajustar melhor à tela:

tac file.txt | \
awk '/^\[This is the ending I was looking for]/,/^\[This is the start]/ { print $0 }' | \
tac

O { print $0 } não é necessário neste comando awk , pois é o comportamento padrão:

tac file.txt | \
awk '/^\[This is the ending I was looking for]/,/^\[This is the start]/' | \
tac

Infelizmente, se você estiver usando um Mac, tac não será instalado por padrão.

    
por 18.07.2018 / 01:01
0

Você pode fazer isso com ex / vi ou ed , o que pode fazer pesquisas inversas, por exemplo,

  • pesquisa (encaminhar) para o padrão final
  • entrar no modo "normal" um lugar marcado lá
  • pesquise de trás para frente para o padrão de início
  • imprimir da linha atual até a marca

ex.

$ ex file << \EOF
/\[This is the ending I was looking for\]
execute "normal! ma\<esc>"
?\[This is the start\]
.,'a p
EOF    
[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

ou

$ ed -s file << \EOF
/\[This is the ending I was looking for\]/;#
ka
?\[This is the start\]?;#
.,'a p
EOF
[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

Como um verso:

printf "/\[This is the ending I was looking for\]/;#\nka\n?\[This is the start\]?;#\n.,'a p\n" | ed -s file
    
por 17.07.2018 / 23:51
0

Uma solução usando o awk é:

rstart='^[[]This is the start[]]'
rend='[[]This is the ending I was looking for[]]'

awk '$0~rstart{i=1;a=""}
     $0~rstart,$0~rend && i==1 {a = a ((a=="")?"":ORS) $0}
     $0~rend{i=0;print(a)}
    ' rstart="$rstart" rend="$rend" infile

Os colchetes estão sendo correspondidos por [[] e []] para evitar o uso de barra invertida \[ (que pode falhar sob algumas condições).

A idéia principal é usar uma variável i (include) como um valor booleano para incluir ou não cada linha no intervalo a ser impresso. Todo o intervalo é acumulado na variável a . Separado com o ORS (Output Record Separator) se a variável a não for nula ( ((a=="")?"":ORS) ).

Isso imprimirá:

[This is the start] of some other data
this is info I want
this is info I want
[This is the ending I was looking for]

Se for necessário que os marcadores de início e fim não sejam impressos, use o mesmo código, mas as linhas de troca 1 e 3:

awk '$0~rend{i=0;print(a)}
     $0~rstart,$0~rend && i==1 {a = a ((a=="")?"":RS) $0}
     $0~rstart{i=1;a=""}
    ' rstart="$rstart" rend="$rend" infile

Que imprimirá:

this is info I want
this is info I want
    
por 18.07.2018 / 03:45