AWK: acessar o grupo capturado a partir do padrão de linha

229

Se eu tiver um comando awk

pattern { ... }

e pattern usa um grupo de captura, como posso acessar a string capturada no bloco?

rampion
fonte
Às vezes (em casos simples) é possível ajustar o separador de campos ( FS) e escolher o que se deseja combinar com a $field. A pré-formatação da entrada também pode ajudar.
Krzysztof Jabłoński
1
Há uma resposta melhor para a pergunta duplicada.
Samuel Edwin Ward
2
Samuel Edwin Ward: Essa é uma boa resposta também! Mas também requer gawk(uma vez que usa gensub).
Rampion

Respostas:

176

Esse foi um passeio pela estrada da memória ...

Substituí awk por perl há muito tempo.

Aparentemente, o mecanismo de expressão regular do AWK não captura seus grupos.

você pode considerar usar algo como:

perl -n -e'/test(\d+)/ && print $1'

o sinalizador -n faz com que o perl faça um loop sobre todas as linhas, como o awk.

Peter Tillemans
fonte
3
Aparentemente, alguém discorda. Esta página da web é de 2005: tek-tips.com/faqs.cfm?fid=5674 Confirma que você não pode reutilizar grupos correspondentes no awk.
Peter Tillemans
3
Eu prefiro 'perl -n -p -e ...' em vez de awk para quase todos os casos de uso, pois é mais flexível, mais poderoso e tem uma sintaxe mais saudável na minha opinião.
Peter Tillemans
15
gawk! = awk. São ferramentas diferentes e gawknão estão disponíveis por padrão na maioria dos lugares.
Oli
6
O OP pediu especificamente uma solução awk, então não acho que seja uma resposta.
Joppe
6
@ Joppe, você não pode dar uma solução awk se não houver solução. Na linha 3, explico que o AWK não suporta grupos de captura e dei uma alternativa, que o OP aparentemente apreciou porque essa resposta foi aceita. Como eu poderia responder melhor a essa pergunta?
precisa
335

Com o gawk, você pode usar a matchfunção para capturar grupos entre parênteses.

gawk 'match($0, pattern, ary) {print ary[1]}' 

exemplo:

echo "abcdef" | gawk 'match($0, /b(.*)e/, a) {print a[1]}' 

saídas cd.

Observe o uso específico do gawk que implementa o recurso em questão.

Para uma alternativa portátil, você pode obter resultados semelhantes com match()e substr.

exemplo:

echo "abcdef" | awk 'match($0, /b[^e]*/) {print substr($0, RSTART+1, RLENGTH-1)}'

saídas cd.

Glenn Jackman
fonte
4
Sim, as variantes gxxx têm muito mais poder e utilidade GNU.
Peter Tillemans
Funciona no BusyBox awk também.
MrMas
32

Isso é algo que eu preciso o tempo todo, então criei uma função bash para isso. É baseado na resposta de Glenn Jackman.

Definição

Adicione isso ao seu .bash_profile etc.

function regex { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'0'}']}'; }

Uso

Capturar regex para cada linha no arquivo

$ cat filename | regex '.*'

Capturar o primeiro grupo de captura de regex para cada linha no arquivo

$ cat filename | regex '(.*)' 1
opsb
fonte
2
Como é diferente de usar grep -o?
bfontaine
@bfontaine Poderia gerar grep -ogrupos capturados?
Olle Härstedt 7/0318
1
@ OlleHärstedt Não, não podia. Ele cobre apenas seu caso de uso quando você não possui grupos de captura. Nesse caso, fica feio com os encadeados grep -o.
precisa saber é
15

Você pode usar o GNU awk:

$ cat hta
RewriteCond %{HTTP_HOST} !^www\.mysite\.net$
RewriteRule (.*) http://www.mysite.net/$1 [R=301,L]

$ gawk 'match($0, /.*(http.*?)\$/, m) { print m[1]; }' < hta
http://www.mysite.net/
Isvara
fonte
12
+1. Além disso, com qualquer awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton
5
É o que a resposta de Glenn Jackman diz , basicamente.
Rampion
1
Ed Morton: isso merece uma resposta de nível superior, eu diria. edit: uhm ... que imprime RewriteRule (.*) http://www.mysite.net/$para mim, que é mais do que o subgrupo.
Rampion
4

Você também pode simular a captura no vanilla awk, sem extensões. Não é intuitivo:

Etapa 1. Use o gensub para localizar correspondências com algum caractere que não apareça na sua string. passo 2. Use divisão contra o personagem. Etapa 3. Todos os outros elementos da matriz dividida são o seu grupo de captura.

$ echo 'ab cb ad' | awk '{split (gensub (/ a ./, SUBSEP "e" SUBSEP ", g", US $ 0), cap, SUBSEP); tampa de impressão [2] "|" tampa [4]; } '
ab | ad
ydrol
fonte
3
Estou quase certo de que gensubé uma gawkfunção específica. O que você obtém do seu awk se digitar awk --version; -?). Boa sorte a todos.
shellter
6
Estou totalmente certo de que o gensub é um gawk-ism, embora o BusyBox awk também o tenha. Essa resposta também poderia ser implementado usando gsub, no entanto:echo 'ab cb ad' | awk '{gsub(/a./,SUBSEP"&"SUBSEP);split($0,cap,SUBSEP);print cap[2]"|"cap[4]}'
dubiousjim
3
gensub () é uma extensão do gawk, o manual do gawk diz claramente isso. Outras variantes do awk também podem implementá-lo, mas ainda não é o POSIX. Tente gawk --posix '{gsub (...)}' e ele irá reclamar
MestreLion
2
@MestreLion, você quer dizer que vai reclamar gawk --posix '{gensub(...)}'.
dubiousjim
1
Apesar de você estar errado sobre o POSIX awk ter a gensubfunção, seu exemplo se aplica a um cenário muito limitado: todo o padrão é agrupado, não pode corresponder a algo como tudo key=(value)quando quero extrair apenas as valuepartes.
Meow
2

Eu lutei um pouco com a criação de uma função bash que envolva a resposta de Peter Tillemans, mas aqui está o que eu vim com:

função regex {perl -n -e "/ $ 1 / && printf \"% s \ n \ "," '$ 1'}

Achei que isso funcionou melhor do que a função bash do opsb para o seguinte argumento de expressão regular, porque não quero que o "ms" seja impresso.

'([0-9]*)ms$'
Wytten
fonte
Eu prefiro essa solução, pois você pode ver as partes do grupo que delimitam a captura e também as omite. No entanto, alguém poderia explicar como isso funciona? Não consigo fazer com que essa sintaxe perl funcione corretamente no BASH, porque não a entendo muito bem - especialmente as aspas duplas / aspas simples$1
Demis
Não é algo que eu tenha feito antes ou depois, mas olhando para trás, o que está fazendo é concatenar duas cadeias de caracteres, sendo a primeira entre aspas duplas (essa primeira sequência contém aspas duplas incorporadas escapadas com barra invertida) e a segunda entre aspas simples . Então o resultado dessa concatenação é fornecido como argumento para perl -e. Além disso, você precisa saber que o primeiro $ 1 (aquele entre aspas duplas) é substituído pelo primeiro argumento da função, enquanto o segundo $ 1 (aquele entre aspas simples) é deixado intocado. Veja este exemplo
wytten
Entendo, isso está fazendo um pouco mais de sentido agora. Então, onde no comando perl está a definição de captura de grupo / grupo de expressão regular? Vejo que você escreveu '([0-9]*)ms$'- isso é fornecido como argumento (e a string, outro argumento)? E a saída de perl -eestá sendo inserida no printfcomando do bash, então, para substituir %s, está certo? Obrigado, espero usar isso.
Demis
1
Você passa uma expressão regular entre aspas simples como o único argumento para a função regex bash. Exemplo
wytten