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


8

Como posso aparar um arquivo (fluxo de entrada do poço) para obter apenas as linhas que vão da primeira ocorrência do padrão fooà ú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 esta saída:

foo
this 
foo
bar
something
something else
foo
bar

3
Fluxo de passagem única ou um arquivo? Isso é muito mais fácil quando o acesso aleatório é permitido. Com um arquivo, você apenas encontraria o primeiro fooe o último bare imprimiria tudo o que havia entre eles. Com um fluxo, você teria que ler até o primeiro fooe armazenar em buffer todas as linhas subseqüentes na memória até o EOF, liberando o buffer sempre que barfor visto. Isso pode significar armazenar em buffer todo o fluxo na memória.
Jw013 12/12/12

Respostas:


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

A correspondência do padrão sed /first/,/second/lê as linhas uma a uma. Quando alguma linha corresponde a /first/ela, ela se lembra e aguarda a primeira correspondência do /second/padrão. Ao mesmo tempo, aplica todas as atividades especificadas para esse padrão. Após esse processo, é iniciado novamente e novamente até o final do arquivo.

Não é disso que precisamos. Precisamos procurar a última correspondência de /second/padrão. Portanto, construímos construções que parecem apenas para a primeira entrada /foo/. Quando encontrado, o ciclo acomeça. Adicionamos nova linha ao buffer de correspondência Ne verificamos se ele 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 /^\n/s/^\n//. Tenho certeza de que existe uma solução muito melhor, infelizmente não veio à minha mente.

Espero que tudo esteja claro.


1
Funciona! Seria super legal se você pudesse nos orientar na construção desse comando. Eu me sinto idiota simplesmente copiar / colar-lo de algum site on-line;)
rahmu

1
Desculpe, não postei a explicação com a resposta. Agora está no post.
apressar

Em algumas sedversões, por exemplo, BSD sed (que é encontrado nos Macs), as tags precisam ser seguidas por uma nova linha ou final de string, portanto é necessário o seguinte ajuste: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Isso também funciona no GNU sed, então eu acho que essa modificação (vários -eargumentos terminar um argumento após o nome de cada ramo) é um bom hábito portátil de se usar ao usar ramos no sed.
Curinga

4

Eu faria isso com um pouco de linha única de 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

3
Se isso fosse código-golfe, você poderia usar em Evez de ee em -00777vez do $/bit (veja perlrun (1)). O que reduziria para:, perl -0777 -nE 'say /(foo.*bar)/s'ainda meio legível.
Thor

1
Eu não sabia sobre essas bandeiras! Estou certo de que, especialmente -0[octal], ele encontrará seu caminho no meu fluxo de trabalho! Obrigado por isso
user1146332 12/12/12

3

Aqui está uma solução GNU sed de duas passagens 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

  • A primeira sedchamada passa infile e localiza a primeira ocorrência de fooe todas as ocorrências subsequentes de bar.
  • Esses endereços são modelados em um novo sedscript com duas invocações de sede um tr. Saída do terceiro sedé [start_address],[end_address]p, sem os colchetes.
  • Chamada final de sedpasses infilenovamente, imprimindo os endereços encontrados e tudo mais.

2

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

Se o arquivo de entrada for enorme, você poderá csplitdividi-lo em pedaços no início fooe, a cada subseqüente bar, montá- los . As peças são chamadas piece-000000000, piece-000000001etc. Escolha um prefixo (aqui piece-) que não entrará em conflito com outros arquivos existentes.

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

(Em sistemas não Linux, você precisará usar um grande número dentro dos aparelhos, por exemplo {999999999}, e passar a -kopção. Esse número é o número de barpeças.)

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

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

ou equivalente

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

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

cat piece-* >output
rm piece-*

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

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done

1

Aqui está outra maneira de sed:

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

Anexa cada linha no /foo/,$intervalo (as linhas que !não estão nesse intervalo são dexcluídas) ao Hespaço antigo. As linhas que não correspondem barsão excluídas. Nas linhas correspondentes, o espaço do padrão é esvaziado, e xalterado com o espaço de espera e a linha vazia inicial no espaço do padrão é removida.

Com entrada enorme e poucas ocorrências, barisso deve ser (muito) mais rápido do que colocar cada linha no espaço do padrão e, a cada vez, verificar o espaço do padrão 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 cabe na memória), você pode simplesmente executar:

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

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

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'

0

Usando qualquer awk em qualquer shell em qualquer sistema UNIX e sem ler o arquivo inteiro ou o fluxo de entrada na memória ao mesmo tempo:

$ 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

0

O Grep também pode fazê-lo (bem, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\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 '\0' '\n'           #  Restore the NULs to newlines.

Para a entrada do corpo da pergunta:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
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.