Como imprimir o padrão regex correspondente usando awk?

109

Usando awk, preciso encontrar uma palavra em um arquivo que corresponda a um padrão regex.

Desejo apenas imprimir a palavra que corresponde ao padrão.

Então, se estiver na linha, tenho:

xxx yyy zzz

E padrão:

/yyy/

Eu quero apenas obter:

yyy

EDIT: graças ao kurumi consegui escrever algo assim:

awk '{
        for(i=1; i<=NF; i++) {
                tmp=match($i, /[0-9]..?.?[^A-Za-z0-9]/)
                if(tmp) {
                        print $i
                }
        }
}' $1

e isso é o que eu precisava :) muito obrigado!

marverix
fonte
1
@maxtaldykin Você poderia mover sua auto-resposta da pergunta para uma resposta separada, por favor?
Kenorb
2
Você não precisa fazer tmp=match($i, /regexp);if(tmp){}, você só deve ser capaz de fazer if(tmp ~ $i){}porque ~significa "corresponde ao regexp".
JustinCB

Respostas:

148

Este é o básico

awk '/pattern/{ print $0 }' file

peça awkpara pesquisar patternusando e //, em seguida, imprima a linha, que por padrão é chamada de registro, denotada por $ 0. Pelo menos leia a documentação .

Se você deseja apenas imprimir a palavra correspondente.

awk '{for(i=1;i<=NF;i++){ if($i=="yyy"){print $i} } }' file
kurumi
fonte
49
Uma vez que printé a ação padrão: awk '/pattern/' fileserá suficiente.
Johnsyweb
18
@Johnsyweb, sim, eu conheço esse fato. Para um iniciante como a Marverix, deve ser mais visual.
kurumi
21
Não duvido do seu conhecimento. A informação pode ser útil para outras pessoas que encontram esta resposta, no entanto.
Johnsyweb,
2
NB: @marverix terá que fazer um pouco mais de lição de casa para fazer o forloop funcionar se (a) "yyy" for uma expressão regular e não uma string reta e (b) se "yyy" não corresponder a um campo inteiro dentro uma gravação.
Johnsyweb
8
Não seria $i=="yyy"; seria $i ~ /yyy/para uma expressão regular.
JustinCB
118

Parece que você está tentando emular o grep -ocomportamento do GNU . Isso fará isso, desde que você queira apenas a primeira correspondência em cada linha:

awk 'match($0, /regex/) {
    print substr($0, RSTART, RLENGTH)
}
' file

Aqui está um exemplo, usando a awkimplementação GNU ():

awk 'match($0, /a.t/) {
    print substr($0, RSTART, RLENGTH)
}
' /usr/share/dict/words | head
act
act
act
act
aft
ant
apt
art
art
art

Leia sobre match, substr, RSTARTe RLENGTHno awkmanual.

Depois disso, você pode desejar estender isso para lidar com várias correspondências na mesma linha.

Johnsyweb
fonte
NB: Para responder a essa última parte, todas as construções necessárias estão na resposta de kurumi e na minha.
Johnsyweb
Ótima resposta. Só gostaria de uma explicação aqui porque sou preguiçoso. Mas é por isso que estou usando o AWK!
lukas.pukenis
E se eu quiser fazer algo com o resultado da correspondência, exceto imprimi-lo? Por exemplo, eu quero adicionar todas as correspondências na matriz.
Evya2005
@ evya2005: Você pode simplesmente substituir a chamada de Ron print pelo trabalho de que precisa.
Johnsyweb
Não está funcionando para mim. apenas trabalho de impressão. você pode me mostrar um exemplo?
Evya2005
36

gawk pode obter a parte correspondente de cada linha usando isso como ação:

{ if (match($0,/your regexp/,m)) print m[0] }

match (string, regexp [, array]) Se array estiver presente, ele é apagado e, em seguida, o elemento zero do array é definido para toda a porção da string correspondida por regexp. Se regexp contém parênteses, os elementos indexados por inteiro da matriz são configurados para conter a parte da string que corresponde à subexpressão entre parênteses correspondente. http://www.gnu.org/software/gawk/manual/gawk.html#String-Functions

royas
fonte
13

Se você está interessado apenas na última linha de entrada e espera encontrar apenas uma correspondência (por exemplo, uma parte da linha de resumo de um comando shell), você também pode tentar este código muito compacto, adotado de Como imprimir correspondências de regexp usando `awk`? :

$ echo "xxx yyy zzz" | awk '{match($0,"yyy",a)}END{print a[0]}'
yyy

Ou a versão mais complexa com um resultado parcial:

$ echo "xxx=a yyy=b zzz=c" | awk '{match($0,"yyy=([^ ]+)",a)}END{print a[1]}'
b

Aviso: a awk match()função com três argumentos só existe em gawk, não emmawk

Aqui está outra boa solução usando um regex lookbehind em grepvez de awk. Esta solução tem requisitos mais baixos para sua instalação:

$ echo "xxx=a yyy=b zzz=c" | grep -Po '(?<=yyy=)[^ ]+'
b
Daniel Alder
fonte
Por que você adicionou "tail -n1"? Isso deve funcionar bem sem ele, não?
Arthur Accioly
1
@ArthurAccioly Correct. Usei o termo para extrair o tempo médio de ida e volta de uma chamada de ping, de onde veio. engraçado que levou 4 anos para descobri-lo;)
Daniel Alder
12

Se Perl for uma opção, você pode tentar o seguinte:

perl -lne 'print $1 if /(regex)/' file

Para implementar a correspondência que não diferencia maiúsculas de minúsculas, adicione o imodificador

perl -lne 'print $1 if /(regex)/i' file

Para imprimir tudo APÓS a partida:

perl -lne 'if ($found){print} else{if (/regex(.*)/){print $1; $found++}}' textfile

Para imprimir a partida e tudo depois da partida:

perl -lne 'if ($found){print} else{if (/(regex.*)/){print $1; $found++}}' textfile
Chris Koknat
fonte
3

Usar o sed também pode ser elegante nessa situação. Exemplo (substitua a linha pelo grupo correspondente "yyy" da linha):

$ cat testfile
xxx yyy zzz
yyy xxx zzz
$ cat testfile | sed -r 's#^.*(yyy).*$#\1#g'
yyy
yyy

Página do manual relevante: https://www.gnu.org/software/sed/manual/sed.html#Back_002dreferences-and-Subexpressions

Konrad Brodzik
fonte
Para não gnu sed, a solução é mais ou menos assim:sed -n 's/^.*\(yyy\).*$/\1/gp' < testfile
Grigory Entin
1
@GrigoryEntin - bsd sed funciona bem com a resposta original. O switch regex estendido suportado pelo POSIX é -E, mas no FreeBSD pelo menos -r é o mesmo que -E (-r adicionado em 2010). De qualquer forma, tente com -E (gnu sed adicionou -E em 4.3)
Juan
3

Fora do tópico, isso também pode ser feito usando o grep, apenas postando aqui caso alguém esteja procurando por uma solução grep

echo 'xxx yyy zzze ' | grep -oE 'yyy'
Zeus
fonte
Maneira simples de pegá-lo, mesmo com regex. Exatamente o que eu precisava. Obrigado!
Marquee
Isso funciona para mim; Meu caso é o seguinte: echo "web_port = 8080, shutdown_port = 8005" | grep -oE "web_port = [0-9] +" # return 8080
Robb Tsang
0

Se você souber em qual coluna está o texto / padrão que está procurando (por exemplo, "yyy"), você pode apenas verificar essa coluna específica para ver se corresponde e imprimi-la.

Por exemplo, dado um arquivo com o seguinte conteúdo, (chamado asdf.txt )

xxx yyy zzz

para imprimir apenas a segunda coluna se ela corresponder ao padrão "yyy", você pode fazer algo assim:

awk '$2 ~ /yyy/ {print $2}' asdf.txt

Observe que isso também corresponderá basicamente a qualquer linha em que a segunda coluna contenha um "yyy", como estes:

xxx yyyz zzz
xxx zyyyz
kimbo
fonte