Como obter todas as linhas entre a primeira e a última ocorrências de padrões?

8

Como posso cortar um arquivo (bem fluxo de entrada) para que eu só pegue as linhas desde a primeira ocorrência do padrão foo até a última ocorrência do padrão bar ?

Por exemplo, considere a seguinte entrada:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Espero que esta saída:

foo
this 
foo
bar
something
something else
foo
bar
    
por rahmu 12.09.2012 / 16:03

7 respostas

6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

O padrão de sed correspondente a /first/,/second/ lê as linhas uma por uma. Quando alguma linha corresponde a /first/ , ela se lembra e aguarda a primeira correspondência para o padrão /second/ . Ao mesmo tempo, aplica todas as atividades especificadas para esse padrão. Depois desse processo começa de novo e de novo até o final do arquivo.

Isso não é o que precisamos. Precisamos olhar para a última correspondência do padrão /second/ . Portanto, construímos uma construção que parece apenas pela primeira entrada /foo/ . Quando encontrado, o ciclo a é iniciado. Adicionamos uma nova linha ao buffer de correspondência com N e verificamos se ela corresponde ao padrão /bar/ . Se isso acontecer, basta imprimi-lo e limpar o buffer de correspondência e o janyway jump para o início do ciclo com ba .

Também precisamos excluir o símbolo de nova linha após a limpeza do buffer com /^\n/s/^\n// . Tenho certeza de que há uma solução muito melhor, infelizmente não veio à minha mente.

Espero que tudo esteja claro.

    
por 12.09.2012 / 17:39
4

Eu faria isso com uma pequena linha Perl.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

rendimentos

foo
this 
foo
bar
something
something else
foo
bar
    
por 12.09.2012 / 16:58
3

Aqui está uma solução de dois passos do GNU sed que não requer muita memória:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Explicação

  • Primeira invocação de sed passa no infile e encontra a primeira ocorrência de foo e todas as ocorrências subseqüentes de bar .
  • Esses endereços são transformados em um novo script sed com duas invocações de sed e um tr . A saída do terceiro sed é [start_address],[end_address]p , sem os colchetes.
  • A invocação final de sed passa novamente o infile , imprimindo os endereços encontrados e tudo mais.
por 12.09.2012 / 18:14
2

Se o arquivo de entrada couber confortavelmente na memória, mantenha-o simples .

Se o arquivo de entrada for grande, você pode usar csplit para dividi-lo peças no primeiro foo e em cada% subseqüentebar, em seguida, montar as peças. As peças são chamadas piece-000000000 , piece-000000001 , etc. Escolha um prefixo (aqui, piece- ) que não colidirá com outros arquivos existentes.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Em sistemas não Linux, você terá que usar um número grande dentro das chaves, por exemplo, {999999999} , e passar a opção -k . Esse número é o número de bar peças.)

Você pode montar todas as peças com cat piece-* , mas isso lhe dará tudo após o primeiro foo . Então remova essa última peça primeiro. Como os nomes de arquivos produzidos por csplit não contêm caracteres especiais, você pode usá-los sem tomar nenhuma precaução especial de cotação, por exemplo, com

rm $(echo piece-* | sed 's/.* //')

ou equivalentemente

rm $(ls piece-* | tail -n 1)

Agora você pode juntar todas as partes e remover os arquivos temporários:

cat piece-* >output
rm piece-*

Se você deseja remover as partes conforme elas são concatenadas para economizar espaço em disco, faça isso em um loop:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done
    
por 13.09.2012 / 02:48
1

Aqui está outra maneira com sed :

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Acrescenta cada linha em /foo/,$ range (as linhas ! não neste intervalo são d eleted) para H old space. Linhas não correspondentes a bar são excluídas. Nas linhas que correspondem, o espaço de padrão é esvaziado, e x foi alterado com o espaço de armazenamento e a linha vazia principal no espaço de padrão é removida.

Com uma entrada enorme e poucas ocorrências de bar , isso deve ser (muito) mais rápido do que puxar cada linha para o espaço de padrões e, a cada vez, verificar o espaço padrão para bar .
Explicado:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Claro, se este é um arquivo (e se encaixa na memória) você pode simplesmente executar:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

porque ed pode pesquisar para a frente e para trás.
Você pode até mesmo ler uma saída de comando no buffer de texto se o seu shell suportar a substituição de processos:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

ou, se não, com gnu ed :

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
    
por 16.07.2015 / 21:01
0

Usando qualquer awk em qualquer shell em qualquer sistema UNIX e sem ler todo o arquivo ou fluxo de entrada na memória de uma só vez:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar
    
por 30.09.2018 / 00:05
0

Grep também pode fazer isso (bem, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '
$ <infile grep -ozP '(?s)foo.*bar' | tr '
<infile grep -ozP '(?s)foo.*bar' | tr '
$ <infile grep -ozP '(?s)foo.*bar' | tr '%pre%' '\n'
foo
this 
foo
bar
something
something else
foo
bar
' '\n' <infile grep -ozP ' # call grep to print only the matching section ('-o') # use NUL for delimiter ('-z') (read the whole file). # And using pcre regex. (?s)foo.*bar # Allow the dot ('.') to also match newlines. ' | tr '%pre%' '\n' # Restore the NULs to newlines.
' '\n' foo this foo bar something something else foo bar
' '\n' <infile grep -ozP ' # call grep to print only the matching section ('-o') # use NUL for delimiter ('-z') (read the whole file). # And using pcre regex. (?s)foo.*bar # Allow the dot ('.') to also match newlines. ' | tr '%pre%' '\n' # Restore the NULs to newlines.

Para a entrada do corpo da pergunta:

%pre%     
por 30.09.2018 / 09:37