Como uso variáveis ​​de shell em um script awk?

289

Eu encontrei algumas maneiras de passar variáveis ​​externas do shell para um awkscript, mas estou confuso sobre 'e ".

Primeiro, tentei com um script de shell:

$ v=123test
$ echo $v
123test
$ echo "$v"
123test

Então tentei o awk:

$ awk 'BEGIN{print "'$v'"}'
$ 123test
$ awk 'BEGIN{print '"$v"'}'
$ 123

Por que a diferença?

Por fim, tentei o seguinte:

$ awk 'BEGIN{print " '$v' "}'
$  123test
$ awk 'BEGIN{print ' "$v" '}'
awk: cmd. line:1: BEGIN{print
awk: cmd. line:1:             ^ unexpected newline or end of string 

Estou confuso sobre isso.

hqjma
fonte
2
Eu gosto do -v como mostrado abaixo, mas este é realmente um ótimo exercício para pensar em como proteger as coisas do shell. Trabalhando com isso, meu primeiro corte usa barras invertidas em espaços e cifrões. Escusado será dizer que os exemplos aqui valeram bem a pena.
Chris
Se sua pesquisa do awk precisar de expressão regular , você não poderá colocar /var/. Em vez disso, use til:awk -v var="$var" '$0 ~ var'
Noam Manos

Respostas:

496

Colocando variáveis ​​de shell em awk

pode ser feito de várias maneiras. Alguns são melhores que outros. Isso deve cobrir a maioria deles. Se você tiver um comentário, por favor deixe abaixo. v1.5


Usando -v (da melhor maneira, mais portátil)

Use a -vopção: (PS use um espaço depois -vou será menos portátil. Por exemplo, awk -v var=não awk -vvar=)

variable="line one\nline two"
awk -v var="$variable" 'BEGIN {print var}'
line one
line two

Isso deve ser compatível com a maioria awke a variável também está disponível no BEGINbloco:

Se você tiver várias variáveis:

awk -v a="$var1" -v b="$var2" 'BEGIN {print a,b}'

Atenção . Como escreve Ed Morton, as seqüências de escape serão interpretadas para que \tse tornem reais tabe não \tse é isso que você procura. Pode ser resolvido usando ENVIRON[]ou acessando-o viaARGV[]

PS Se você gosta de três barras verticais como separador |||, elas não podem ser escapadas, então use-F"[|][|][|]"

Exemplo de como obter dados de uma pousada de programa / função para awk(aqui a data é usada)

awk -v time="$(date +"%F %H:%M" -d '-1 minute')" 'BEGIN {print time}'

Variável após bloco de código

Aqui nós obtemos a variável após o awkcódigo. Isso funcionará bem, desde que você não precise da variável no BEGINbloco:

variable="line one\nline two"
echo "input data" | awk '{print var}' var="${variable}"
or
awk '{print var}' var="${variable}" file
  • Adicionando várias variáveis:

awk '{print a,b,$0}' a="$var1" b="$var2" file

  • Dessa forma, também podemos definir diferentes separadores de campo FSpara cada arquivo.

awk 'some code' FS=',' file1.txt FS=';' file2.ext

  • Variável após o bloco de código não funcionará para o BEGINbloco:

echo "input data" | awk 'BEGIN {print var}' var="${variable}"


Here-string

A variável também pode ser adicionada ao awkuso de uma string here de shells que os suportam (incluindo o Bash):

awk '{print $0}' <<< "$variable"
test

É o mesmo que:

printf '%s' "$variable" | awk '{print $0}'

PS trata a variável como uma entrada de arquivo.


ENVIRON entrada

Enquanto o TrueY escreve, você pode usar o ENVIRONpara imprimir variáveis ​​de ambiente . Definindo uma variável antes de executar o AWK, você pode imprimi-la assim:

X=MyVar
awk 'BEGIN{print ENVIRON["X"],ENVIRON["SHELL"]}'
MyVar /bin/bash

ARGV entrada

Como escreve Steven Penny, você pode usar ARGVpara colocar os dados no awk:

v="my data"
awk 'BEGIN {print ARGV[1]}' "$v"
my data

Para inserir os dados no próprio código, não apenas o BEGIN:

v="my data"
echo "test" | awk 'BEGIN{var=ARGV[1];ARGV[1]=""} {print var, $0}' "$v"
my data test

Variável dentro do código: USE COM CUIDADO

Você pode usar uma variável dentro do awkcódigo, mas é confuso e difícil de ler e, como Charles Duffyaponta, esta versão também pode ser vítima de injeção de código. Se alguém adicionar coisas ruins à variável, ela será executada como parte do awkcódigo.

Isso funciona extraindo a variável dentro do código, tornando-se parte dela.

Se você deseja fazer uma awkalteração dinâmica dinamicamente com o uso de variáveis, pode fazê-lo desta maneira, mas NÃO o use para variáveis ​​normais.

variable="line one\nline two"
awk 'BEGIN {print "'"$variable"'"}'
line one
line two

Aqui está um exemplo de injeção de código:

variable='line one\nline two" ; for (i=1;i<=1000;++i) print i"'
awk 'BEGIN {print "'"$variable"'"}'
line one
line two
1
2
3
.
.
1000

Você pode adicionar muitos comandos awkdessa maneira. Até mesmo travar com comandos não válidos.


Informação extra:

Uso de aspas duplas

É sempre bom fazer aspas duplas. "$variable"
Caso contrário, várias linhas serão adicionadas como uma única linha longa.

Exemplo:

var="Line one
This is line two"

echo $var
Line one This is line two

echo "$var"
Line one
This is line two

Outros erros que você pode obter sem aspas:

variable="line one\nline two"
awk -v var=$variable 'BEGIN {print var}'
awk: cmd. line:1: one\nline
awk: cmd. line:1:    ^ backslash not last character on line
awk: cmd. line:1: one\nline
awk: cmd. line:1:    ^ syntax error

E com aspas simples, ele não expande o valor da variável:

awk -v var='$variable' 'BEGIN {print var}'
$variable

Mais informações sobre AWK e variáveis

Leia este FAQ .

Jotne
fonte
2
"bagunçado e difícil de ler" ignora a preocupação de segurança mais importante da injeção de código ao substituir diretamente as strings pelo código awk.
Charles Duffy
lendo a resposta acima, posso executar meu script sem erros, mas ele não funciona: awk -v repo = "$ 1" -v tag = "$ 2" '{sub (/ image: registryabx.azurecr.io \ / { repositório de impressão}: ([a-z0-9] +) $ /, "image: registryabc.azurecr. io / {repositório de impressão}: {print tag}");} 1 './services/appscompose.yaml >> newcompose.yaml. É por causa do parêntese aninhado {?
Darion Badlydone 29/10/19
@DarionBadlydone Experimente isso awk -v repo="$1" -v tag="$2" 'BEGIN {print "repo="repo,"tag="tag}'. Verá se imprime a variável. Poste uma pergunta, se você não conseguir descobrir.
JOTNE
@ Jotne sim, ele imprime os valores, então tentei desta maneira: awk -v repo = "$ 1" -v tag = "$ 2" '{print "{sub (/ image: registryabc.azurecr.io/"repo" :( [a-z0-9] +) $ /, \ "imagem: registryabc.azurecr.io/"repo":"tag"\");}1"} './services/appscompose.yaml >> newcompose.yaml mas não está funcionando conforme o aspecto. Ele substitui cada linha do arquivo de origem pelo comando impresso
Darion Badlydone 29/10/19
@Jotne eu fiz isso com sed, obrigado de qualquer maneira
Darion Badlydone
28

Parece que o bom e velho ENVIRON o hash embutido não é mencionado. Um exemplo de seu uso:

$ X=Solaris awk 'BEGIN{print ENVIRON["X"], ENVIRON["TERM"]}'
Solaris rxvt
TrueY
fonte
4
Essa é uma boa sugestão, pois passa os dados literalmente. -vnão funciona quando o valor contém barras invertidas.
aquele outro cara
2
@thatotherguy Eu não sabia disso! Eu pensei que se eu usar, awk -v x='\c\d' ...então ele será usado corretamente. Mas quando xé impresso awk deixa cair a famosa: awk: warning: escape sequence '\c' treated as plain 'c'mensagem de erro ... Obrigado!
TrueY
Funciona corretamente - nesse contexto, significa expandir sequências de escape, porque -vfoi assim que foi projetado para funcionar, para que você possa usar \ta variável e corresponder a uma guia literal nos dados, por exemplo. Se esse não é o comportamento que você deseja, não -vo usa ARGV[]ou ENVIRON[].
Ed Morton
9

Use qualquer um destes, dependendo de como você deseja manipular barras invertidas nas variáveis ​​do shell ( avaré uma variável awk, svaré uma variável do shell):

awk -v avar="$svar" '... avar ...' file
awk 'BEGIN{avar=ARGV[1];ARGV[1]=""}... avar ...' "$svar" file

Consulte http://cfajohnson.com/shell/cus-faq-2.html#Q24 para obter detalhes e outras opções. O primeiro método acima é quase sempre sua melhor opção e possui a semântica mais óbvia.

Ed Morton
fonte
6

Você pode passar na opção de linha de comando -v com um nome de variável ( v) e um valor ( =) da variável de ambiente ( "${v}"):

% awk -vv="${v}" 'BEGIN { print v }'
123test

Ou, para torná-lo mais claro (com muito menos vs):

% environment_variable=123test
% awk -vawk_variable="${environment_variable}" 'BEGIN { print awk_variable }'
123test
Johnsyweb
fonte
3

Você pode utilizar o ARGV:

v=123test
awk 'BEGIN {print ARGV[1]}' "$v"

Observe que, se você continuar no corpo, precisará ajustar o ARGC:

awk 'BEGIN {ARGC--} {print ARGV[2], $0}' file "$v"
Steven Penny
fonte
1

Acabei de alterar a resposta de @ Jotne para "for loop".

for i in `seq 11 20`; do host myserver-$i | awk -v i="$i" '{print "myserver-"i" " $4}'; done
edib
fonte
1
Isso apenas parece ser outra ilustração de como usar a -vopção do Awk, que já foi mencionada em muitas das respostas existentes. Se você deseja mostrar como executar o Awk em um loop, essa é realmente uma pergunta diferente.
Tripleee 07/07/19
0

Eu tive que inserir a data no início das linhas de um arquivo de log e é feito como abaixo:

DATE=$(date +"%Y-%m-%d")
awk '{ print "'"$DATE"'", $0; }' /path_to_log_file/log_file.log

Pode ser redirecionado para outro arquivo para salvar

Sina
fonte
As aspas duplas - aspas simples - aspas duplas eram exatamente o que eu precisava para fazer o meu funcionar.
user53029
2
Isso já foi mencionado na resposta aceita como um método que você não deve usar devido a vulnerabilidades de injeção de código. Portanto, as informações aqui são redundantes (já descritas na resposta aceita) e incompletas (não mencionam os problemas com esse método).
Jason S #