Como selecionar linhas entre dois padrões de marcador que podem ocorrer várias vezes com awk / sed

119

Usando awk ou sedcomo posso selecionar linhas que estão ocorrendo entre dois padrões de marcador diferentes? Pode haver várias seções marcadas com esses padrões.

Por exemplo: Suponha que o arquivo contenha:

abc
def1
ghi1
jkl1
mno
abc
def2
ghi2
jkl2
mno
pqr
stu

E o padrão inicial é abce o padrão final é mno Então, eu preciso da saída como:

def1
ghi1
jkl1
def2
ghi2
jkl2

Estou usando o sed para corresponder ao padrão uma vez:

sed -e '1,/abc/d' -e '/mno/,$d' <FILE>

Existe alguma maneira sedou awk fazê-lo repetidamente até o final do arquivo?

dvai
fonte

Respostas:

188

Use awkcom uma bandeira para acionar a impressão quando necessário:

$ awk '/abc/{flag=1;next}/mno/{flag=0}flag' file
def1
ghi1
jkl1
def2
ghi2
jkl2

Como é que isso funciona?

  • /abc/corresponde a linhas com esse texto, assim como /mno/.
  • /abc/{flag=1;next}define flagquando o texto abcé encontrado. Então, ele pula a linha.
  • /mno/{flag=0}desativa flagquando o texto mnoé encontrado.
  • A final flagé um padrão com a ação padrão, que é print $0: se flagfor igual a 1, a linha será impressa.

Para obter uma descrição e exemplos mais detalhados, junto com os casos em que os padrões são mostrados ou não, consulte Como selecionar linhas entre dois padrões? .

fedorqui 'Então pare de prejudicar'
fonte
30
Se você quiser imprimir tudo entre e incluindo o padrão, poderá usá-lo awk '/abc/{a=1}/mno/{print;a=0}a' file.
Scai
6
Sim, @scai! ou até mesmo awk '/abc/{a=1} a; /mno/{a=0}' file- com isso, colocando a acondição antes de /mno/avaliarmos a linha como verdadeira (e imprimi-la) antes de definir a=0. Desta forma, podemos evitar escrever print.
fedorqui 'Então pare de prejudicar'
12
@scai @fedorqui Para incluindo a saída padrão, você pode fazerawk '/abc/,/mno/' file
JOTNE
1
@hkasera awk '/abc/{flag=1}/mno/{flag=0}flag' filedeve fazer.
fedorqui 'Então, pare de prejudicar'
2
@EirNym, um cenário estranho que pode ser tratado de maneiras muito diferentes: quais linhas você gostaria de imprimir? Provavelmente awk 'flag; /PAT1/{flag=1; next} /PAT1/{flag=0}' filedaria.
fedorqui 'SO stop prejudying'
45

Usando sed:

sed -n -e '/^abc$/,/^mno$/{ /^abc$/d; /^mno$/d; p; }'

o -n opção significa não imprimir por padrão.

O padrão procura por linhas que contenham apenas just abc-just mnoe depois executa as ações no { ... }. A primeira ação exclui oabc linha; o segundo a mnolinha; e pimprime as linhas restantes. Você pode relaxar as expressões regulares, conforme necessário. Quaisquer linhas fora do intervalo de abc.. mnosimplesmente não são impressas.

Jonathan Leffler
fonte
Obrigado pela resposta e pela explicação! :)
dvai
@JonathanLeffler posso saber qual é a finalidade do uso-e
Kasun Siyambalapitiya
1
@KasunSiyambalapitiya: Principalmente significa que eu gosto de usá-lo. Formalmente, especifica que o próximo argumento é (parte do) script que seddeve ser executado. Se você quiser ou precisar usar vários argumentos para incluir o script inteiro, deverá usar -eantes de cada argumento; caso contrário, é opcional (mas explícito).
Jonathan Leffler
@JonathanLeffler Thanks
Kasun Siyambalapitiya
Agradável! (Prefiro sed sobre awk.) Ao usar expressões regulares complexas, seria bom não ter que repeti-las. Não é possível excluir a primeira / última linha do intervalo "selecionado"? Ou para aplicar primeiro da todas as linhas até a primeira partida e depois da todas as linhas que começam com a segunda partida?
hans_meine
18

Isso pode funcionar para você (GNU sed):

sed '/^abc$/,/^mno$/{//!b};d' file

Exclua todas as linhas, exceto aquelas entre as linhas inicial abcemno

potong
fonte
!d;//dgolfs 2 caracteres melhor :-) stackoverflow.com/a/31380266/895245
Ciro Santilli郝海东冠状病六四事件法轮功
Isso é incrível. Os {//!b}impede que o abce mnosejam incluídos na saída, mas eu não consigo descobrir como. Você poderia explicar?
Brendan
1
@Brendan, a instrução //!blê se a linha atual não é uma das linhas que correspondem ao intervalo, quebra e, portanto, imprime essas linhas, caso contrário todas as outras linhas são excluídas.
Potong
13
sed '/^abc$/,/^mno$/!d;//d' file

golfs dois caracteres melhor do que ppotong {//!b};d

As barras vazias indicam //: "reutilizar a última expressão regular usada". e o comando faz o mesmo que o mais compreensível:

sed '/^abc$/,/^mno$/!d;/^abc$/d;/^mno$/d' file

Este parece ser POSIX :

Se um ER estiver vazio (isto é, nenhum padrão é especificado), sed deve se comportar como se o último ER usado no último comando aplicado (como um endereço ou como parte de um comando substituto) tivesse sido especificado.

Ciro Santilli adicionou uma nova foto
fonte
1
Eu acho que a segunda solução vai acabar com nada, pois o segundo comando também é um intervalo. No entanto, parabéns pelo primeiro.
Potong
@potong true! Eu tenho que estudar mais porque o primeiro funciona. Obrigado!
Ciro Santilli escreveu
7

A partir dos links da resposta anterior, o que fez por mim, rodando kshno Solaris, foi o seguinte:

sed '1,/firstmatch/d;/secondmatch/,$d'
  • 1,/firstmatch/d: da linha 1 até a primeira vez que você encontrar firstmatch , exclua.
  • /secondmatch/,$d: desde a primeira ocorrência de secondmatch até o final do arquivo, exclua.
  • O ponto e vírgula separa os dois comandos, que são executados em sequência.
FanDeLaU
fonte
Apenas curioso, por que o limitador de intervalo ( 1,) vem antes /firstmatch/? Eu estou supondo que isso também poderia ser redigido '/firstmatch/1,d;/secondmatch,$d'?
Luke Davis
2
Com "1, / firstmatch / d", você está dizendo "da linha 1 até a primeira vez que encontrar 'firstmatch', exclua". Considerando que, com "/ secondmatch /, $ d", você diz "desde a primeira ocorrência de 'secondmatch' até o final do arquivo, exclua". o ponto e vírgula separa os dois comandos, que são executados em sequência.
FanDeLaU
2
perl -lne 'print if((/abc/../mno/) && !(/abc/||/mno/))' your_file
Vijay
fonte
É bom saber o equivalente em perl, pois é uma boa alternativa para o awk e o sed.
Akhan
2

algo assim funciona para mim:

file.awk:

BEGIN {
    record=0
}

/^abc$/ {
    record=1
}

/^mno$/ {
    record=0;
    print "s="s;
    s=""
}

!/^abc|mno$/ {
    if (record==1) {
        s = s"\n"$0
    }   
}

usando: awk -f file.awk data ...

edit: O_o solução fedorqui é muito melhor / mais bonita que a minha.

pataluc
fonte
3
No GNU, awk if (record=1)deve ser if (record==1), ou seja, duplo = - veja operadores de comparação gawk
George Hawkins
2

Resposta de Don_crissti de Mostrar apenas texto entre 2 padrões de correspondência ?

firstmatch="abc"
secondmatch="cdf"
sed "/$firstmatch/,/$secondmatch/!d;//d" infile

que é muito mais eficiente que o aplicativo da AWK, veja aqui .

Léo Léopold Hertz 준영
fonte
Não acho que vincular as comparações de tempo faça muito sentido aqui, uma vez que os requisitos das perguntas são bem diferentes, daí as soluções.
fedorqui 'SO stop prejudying'
2
Discordo, porque devemos ter alguns critérios para comparar as respostas. Apenas alguns têm aplicativos SED.
Léo Léopold Hertz,
0

Tentei usar awkpara imprimir linhas entre dois padrões enquanto pattern2 também corresponde a pattern1 . E a linha pattern1 também deve ser impressa.

por exemplo, fonte

package AAA
aaa
bbb
ccc
package BBB
ddd
eee
package CCC
fff
ggg
hhh
iii
package DDD
jjj

deve ter uma saída de

package BBB
ddd
eee

Onde pattern1 é package BBB, pattern2 é package \w*. Observe que CCCnão é um valor conhecido, portanto não pode ser literalmente correspondido.

Nesse caso, nem o @scai awk '/abc/{a=1}/mno/{print;a=0}a' filenem o @fedorqui awk '/abc/{a=1} a; /mno/{a=0}' filefuncionam para mim.

Finalmente, eu consegui resolver isso awk '/package BBB/{flag=1;print;next}/package \w*/{flag=0}flag' file, haha

Um pouco mais de esforço resulta em awk '/package BBB/{flag=1;print;next}flag;/package \w*/{flag=0}' fileimprimir também a linha pattern2, ou seja,

package BBB
ddd
eee
package CCC
Final de semana
fonte