Imprime início e fim entre dois padrões, excluindo o fim do intervalo [duplicado]

0

Eu quero usar o padrão sed -n "/START PATTERN/,/END PATTERN/p" file.txt para pesquisar em um arquivo.

file.txt content is

~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~3.~ ~output~.
~keyword blablabla~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~keyword blablabla2~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~3.~ ~not the output~.
~4.~ ~not the output~.
~blablabla~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~3.~ ~not the output~.
~4.~ ~not the output~.

O que eu espero como saída é

~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~3.~ ~output~.

Portanto, o padrão inicial é keyword entre ~ seguido por qualquer caractere . , por isso é /~keyword~./

O padrão final é ~ seguido por qualquer caractere alfabético e, em seguida, qualquer caractere . .

Quando executo sed -n "/~keyword~./,/[~][[:alpha:]]./p" file.txt , a saída é

~keyword~, ~output~.
~1.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.

A segunda e terceira linhas não estão imprimindo na saída, então minha pergunta é o que há de errado com a minha abordagem? Eu inspirei isso usando a solução fornecida aqui

Eu também tentei sed "/~keyword~./,/[~][[:alpha:]]./!d;//d" file.txt , que resulta em saída vazia ( inspirado nesta questão )

Esta questão é diferente com a questão marcada como duplicada porque eu perguntei especificamente sobre o uso de sed para expressão regular. Considerando isso, se você acha que está duplicado, marque-o como duplicado.

    
por Woeitg 02.04.2016 / 18:02

3 respostas

3

Vamos ver se sed é a ferramenta certa para este trabalho:

sed '/^~[[:alpha:]].*/!{               # if line doesn't match this pattern
H                                      # append it to hold space
$!d                                    # and delete it if it's not the last line
b end                                  # else branch to label end
}
//b end                                # if line matches, branch to label end
: end                                  # label end
x                                      # exchange pattern space w. hold space
/^~keyword~.*/p                        # if pattern space matches, print it
d' infile                              # delete pattern space

Com gnu sed , você pode escrever como um verso:

sed '/^~[[:alpha:]].*/!{H;$!d;b end};//b end;: end;x;/^~keyword~.*/p;d' infile
    
por 02.04.2016 / 20:47
1

Um intervalo delimitado por padrão /P1/,/P2/ , como o que você está usando, começa em (e inclui) a linha correspondente a /P1/ e termina em (e inclui) a linha correspondente a /P2/ .

Seus padrões não estão ancorados no início da linha (você usaria um ^ no regex para isso), então eles podem corresponder em qualquer lugar na linha.
Seu padrão "final" /[~][[:alpha:]]./ corresponde às linhas de dados que você deseja manter (especificamente a parte "tutt ~ ou "), então o intervalo termina logo na primeira linha de dados.

Eu sugeriria que seu intervalo terminasse na primeira linha que não corresponde ao padrão de dados, mas como sed não suporta intervalos sobrepostos, isso tornaria impossível imprime "blocos" consecutivos (como o bloco 1 e o bloco 2 no seu exemplo). (O primeiro bloco incluiria a primeira linha do segundo bloco.)

Posso te interessar pelo nosso lord e salvador awk ? ;)

awk '
    BEGIN {
        inrange = 0
    }
    /^~[[:alpha:]]/ {
        inrange = 0
    }
    /^~keyword~/ {
        inrange = 1
    }
    {
        if (inrange) {
            print
        }
    }'

Uma explicação pode estar em ordem:

  • O script awk acima analisa a entrada (de um arquivo ou stdin ) linha por linha, assim como o sed faz.
  • No início (= antes de processar a primeira linha), ele define um sinalizador como "não devemos imprimir a linha atual".
  • Quando a linha atual corresponde ao padrão que você deu para "primeira linha após um bloco", ela também define o sinalizador como "não imprimir".
  • Quando a linha atual corresponde ao padrão que você deu para "primeira linha de um bloco", ela define o sinalizador como "imprimir".
  • Dependendo do sinalizador, imprime ou não a linha atual.

Você pode até mesmo excluir as linhas de "início de bloco" apenas reorganizando a ordem das verificações (ou seja, imprimir / não imprimir primeiro, verificar se a linha atual é um início de bloco depois).

As quebras de linha no script awk também são opcionais, mas melhoram muito a legibilidade.

    
por 02.04.2016 / 18:29
1

sed não é a ferramenta certa para essa tarefa

... mas isso não significa que você não possa abusar dele para fazer seus lances:

sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt | sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}'

Então, depois de se deitar em uma sala escura para se recuperar dessa abominação, veja o que ela faz:

O que queremos alcançar?
Extraia "blocos" de um arquivo, onde cada "bloco" inicia com uma linha correspondente a regex R1 ("linhas iniciais") e termina com a linha que precede a próxima ocorrência de regex R2 ("linhas terminadoras").

Portanto, use apenas intervalos de padrões de sed , onde está o problema?
R2 é um subconjunto de R1, então nossas "linhas terminadoras" podem ser o começo de novos blocos. sed não suporta blocos sobrepostos.

Portanto, crie um regex que corresponda ao R2, mas não corresponda ao R1.
Isso exigiria asserções de comprimento zero, que sed não tem. (Lembra como eu disse que sed não era a ferramenta certa para isso?)

Solução: Se procurar pela "linha terminadora" engole as "linhas iniciais", apenas duplique as "linhas iniciais".
Isso funcionará, mas não devemos duplicar a primeira "linha de início", senão vamos ver cada par duplicado como um bloco. 1

sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt

= Imprime todas as linhas começando na linha número 2 (ou seja, tudo, exceto a linha 1). Também imprima linhas uma segunda vez se elas corresponderem a R1. Eu vou chegar ao s/$/§/ daqui a pouco.

Agora que temos blocos claramente delimitados, use um intervalo de padrões para imprimir todas as linhas entre iniciantes e terminadores de bloco: sed -n '/^~keyword~./,/^~[[:alpha:]]./p'

Oh, espere, isso inclui as linhas terminator. Stack Overflow para o resgate .
Mas não podemos simplesmente pular todas as linhas que combinam com R2 - lembre-se que R1 ⊂ R2, então remover as linhas terminadoras também removeria as linhas iniciais.

"Felizmente", sed tem ramificação. Que tal imprimirmos tudo que corresponda a R1 e só descartamos as correspondências para R2 depois ?

sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~./b print; /^~[[:alpha:]]./b; :print p}'

Ótimo, agora estamos imprimindo nossas linhas de início duplicadas quando elas são uma linha de terminação ... Se ao menos houvesse uma maneira de distinguir as linhas de início originais e suas duplicatas…

É por isso que temos que s/$/§/ : adiciona § no final de cada linha de início duplicada (note que as linhas iniciais duplicadas do § terminarão sendo as que iniciam um bloco, e as unidas As linhas iniciais serão os blocos termais que são imediatamente seguidos por outro bloco.

Agora, temos todas as informações necessárias para fazer uma verificação e ramificação mais detalhada:

Para todas as linhas dentro de um intervalo de blocos…

  • Verifique se a linha corresponde a R1 e tem um § à direita.
    Em caso afirmativo, remova o § e salte para imprimir a linha.
  • Caso contrário (ou seja, se não pularmos), remova todas as linhas que correspondam a R2 ignorando todos os outros comandos (incluindo a impressão).
  • Finalmente, imprima a linha atual.
{/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}

Resultado final:

sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt | sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}'

No entanto, isso pressupõe que a primeira linha de início do arquivo (correspondente a R1) esteja na linha 1 (lembre-se de que essa é a única linha que excluímos ao duplicar as linhas iniciais). Se não for, você receberá pares limpos, mas nenhum dado:

~keyword~, ~output~.
~keyword~, ~output~.

Você provavelmente poderia adicionar mais correspondência e ramificação para contornar isso, mas realmente…

use apenas awk .

    
por 02.04.2016 / 19:40