Passe variável de shell como um / padrão / para awk

59

Tendo o seguinte em uma das minhas funções shell:

function _process () {
  awk -v l="$line" '
  BEGIN {p=0}
  /'"$1"'/ {p=1}
  END{ if(p) print l >> "outfile.txt" }
  '
}

, portanto, quando chamado como _process $arg, $argé passado como $1e usado como padrão de pesquisa. Funciona assim, porque o shell se expande $1no lugar do padrão awk! Também lpode ser usado dentro do programa awk, sendo declarado com -v l="$line". Tudo bem.

É possível, da mesma maneira, fornecer um padrão para pesquisar como uma variável?

A seguir não funcionará,

awk -v l="$line" -v search="$pattern" '
  BEGIN {p=0}
  /search/ {p=1}
  END{ if(p) print l >> "outfile.txt" }
  '

, como awk não interpretará /search/como uma variável, mas literalmente.

branquito
fonte

Respostas:

46

Use o ~operador do awk e você não precisará fornecer uma regex literal no lado direito:

function _process () {
    awk -v l="$line" -v pattern="$1" '
        $0 ~ pattern {p=1} 
        END {if(p) print l >> "outfile.txt"}
    '  
}

Embora isso seja mais eficiente (não é necessário ler o arquivo inteiro)

function _process () {
    grep -q "$1" && echo "$line"
}

Dependendo do padrão, você pode querer grep -Eq "$1"

Glenn Jackman
fonte
É exatamente isso que resolve isso da maneira que eu queria (1º exemplo), porque mantém a semântica, que era meu objetivo. Obrigado.
21914 branquito
1
Não notei a remoção do bloco BEGIN: uma variável não atribuída é tratada como 0 em um contexto numérico ou a sequência vazia, caso contrário. Assim, uma variável não atribuída será falso emif (p) ...
glenn jackman
sim, notei que ele precisa ser definido no bloco BEGIN para zero a cada vez, pois serve como um comutador. Mas, curiosamente, eu tentei agora usar script $0 ~ pattern, e ele não funciona, no entanto, com /'"$1"'/ele funciona !? : O
branquito 21/03
talvez tenha algo a ver com a maneira como $lineé recuperada, a pesquisa de padrões é feita na saída de whois $line, $linevindo do arquivo em um bloco WHILE DO.
21914 branquito
Por favor, mostre o conteúdo de $line- faça-o na sua pergunta para obter a formatação correta.
glenn jackman
17
awk  -v pattern="$1" '$0 ~ pattern'

Tem um problema que awkexpande as seqüências de escape ANSI C (como \npara nova linha, \ffeed de formulário, \\barra invertida e assim por diante) em $1. Portanto, torna-se um problema se $1contiver caracteres de barra invertida que são comuns em expressões regulares (no GNU awk4.2 ou superior, valores que começam com @/e terminam /também são um problema ). Outra abordagem que não sofre com esse problema é escrevê-lo:

PATTERN=$1 awk '$0 ~ ENVIRON["PATTERN"]'

O quão ruim será depende da awkimplementação.

$ nawk -v 'a=\.' 'BEGIN {print a}'
.
$ mawk -v 'a=\.' 'BEGIN {print a}'
\.
$ gawk -v 'a=\.' 'BEGIN {print a}'
gawk: warning: escape sequence `\.' treated as plain `.'
.
$ gawk5.0.1 -v 'a=@/foo/' BEGIN {print a}'
foo

Todos awks funcionam da mesma maneira para seqüências de escape válidas:

$ a='\\-\b' awk 'BEGIN {print ENVIRON["a"]}' | od -tc
0000000   \   \   -   \   b  \n
0000006

(conteúdo do $apassado como está)

$ awk -v a='\\-\b' 'BEGIN {print a}' | od -tc
0000000   \   -  \b  \n
0000004

( \\alterado para \e \balterado para um caractere de backspace).

Stéphane Chazelas
fonte
Então você está dizendo que, se por exemplo o padrão fosse \d{3}encontrar três dígitos, isso não funcionaria como o esperado, se eu o entendesse bem?
21914 branquito
2
para o \dqual não é uma sequência de escape C válida, que depende da sua awkimplementação (execute awk -v 'a=\d{3}' 'BEGIN{print a}'para verificar). Mas para \` or \ b , yes definitely. (BTW, I don't know of any awk implementations that understands \ d` significa um dígito).
Stéphane Chazelas
ele diz: awk warning - escape sequence \d' treated as plain d 'd {3}, então acho que teria um problema nesse caso?
21914 branquito
1
Desculpe, meu mal, tive um erro de digitação na minha resposta. O nome da variável de ambiente deve corresponder ENVIRON["PATTERN"]à PATTERNvariável de ambiente. Se você quiser usar uma variável shell, precisará exportá-la primeiro ( export variable) ou usar a ENV=VALUE awk '...ENVIRON["ENV"]'sintaxe de passagem env-var, como na minha resposta.
Stéphane Chazelas 21/03
1
Porque você precisa exportar uma variável de shell para que ela seja passada no ambiente para um comando.
Stéphane Chazelas 21/03
5

Tente algo como:

awk -v l="$line" -v search="$pattern" 'BEGIN {p=0}; { if ( match( $0, search )) {p=1}}; END{ if(p) print l >> "outfile.txt" }'
Hunter Eidson
fonte
Se isso se comportar da mesma forma que /regex/em termos de encontrar padrão, essa pode ser uma boa solução. Eu vou tentar.
21914 branquito
1
Os testes rápidos eu corri pareciam funcionar o mesmo, mas eu não vou nem começar a garantir que ... :)
Hunter Eidson
0

Não, mas você pode simplesmente interpolar o padrão na string de aspas duplas que passa para o awk:

awk -v l="$line" "BEGIN {p=0}; /$pattern/ {p=1}; END{ if(p) print l >> \"outfile.txt\" }"

Observe que agora você precisa escapar do literal awk entre aspas duplas, mas ainda é a maneira mais simples de fazer isso.

Kilian Foth
fonte
É seguro assim se $patterncontiver espaços, meu exemplo acima funcionará, pois $ 1 é protegido com aspas duplas "$ 1", mas não descarte o que acontece no seu caso.
21914 branquito
2
Seu exemplo original termina a sequência de aspas simples no segundo ', depois protege as $1aspas duplas e, em seguida, coloca outra sequência de aspas simples na segunda metade do programa awk. Se bem entendi, isso deve ter exatamente o mesmo efeito que proteger as $1aspas simples externas - o awk nunca vê as aspas duplas que você coloca em torno dele.
precisa saber é o seguinte
4
Mas se $patterncontém ^/ {system("rm -rf /")};, então você está com um grande problema.
Stéphane Chazelas 21/03
essa desvantagem é apenas essa abordagem, tendo tudo "" envolvido?
21914 branquito
-3

Você pode usar a função eval, que resolve neste exemplo a variável nets antes da execução do awk.

nets="searchtext"
eval "awk '/"${nets}"/'" file.txt
Noxy
fonte