如何在 awk 脚本中使用 shell 变量?

Posted

技术标签:

【中文标题】如何在 awk 脚本中使用 shell 变量?【英文标题】:How do I use shell variables in an awk script? 【发布时间】:2022-01-18 00:00:04 【问题描述】:

我找到了一些将外部 shell 变量传递给 awk 脚本的方法,但我对 '" 感到困惑。

首先,我尝试了一个 shell 脚本:

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

然后尝试了awk:

$ awk 'BEGINprint "'$v'"'
$ 123test
$ awk 'BEGINprint '"$v"''
$ 123

为什么会有差异?

最后我尝试了这个:

$ awk 'BEGINprint " '$v' "'
$  123test
$ awk 'BEGINprint ' "$v" ''
awk: cmd. line:1: BEGINprint
awk: cmd. line:1:             ^ unexpected newline or end of string 

我对此感到困惑。

【问题讨论】:

我喜欢下面显示的 -v,但这确实是一个很好的练习,可以思考如何保护东西免受 shell 的影响。通过这个,我的第一个剪辑在空格和美元符号上使用反斜杠。不用说这里的例子很值得我花时间。 相关:Difference between single and double quotes in awk. 如果你的awk搜索需要正则表达式,你不能放/var/。相反,使用波浪号:awk -v var="$var" '$0 ~ var' 【参考方案1】:

#获取shell变量到awk 可以通过多种方式完成。有些比其他更好。这应该涵盖其中的大部分。如果您有意见,请在下方留言。 v1.5


使用-v(最好的方式,最便携)

使用-v 选项:(P.S. 在-v 之后使用一个空格,否则它的可移植性会降低。例如,awk -v var= 不是awk -vvar=

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

这应该与大多数awk 兼容,并且该变量在BEGIN 块中也可用:

如果你有多个变量:

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

警告。正如 Ed Morton 所写,转义序列将被解释,所以 \t 变成真正的 tab 而不是 \t 如果这是您搜索的内容。可以通过ENVIRON[]解决或者通过ARGV[]访问

PS如果你喜欢三个竖线作为分隔符|||,它不能被转义,所以使用-F"[|][|][|]"

从程序/函数客栈获取数据到awk的示例(此处使用日期)

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

将shell变量的内容测试为正则表达式的示例:

awk -v var="$variable" '$0 ~ varprint "found it"'

代码块后的变量

这里我们得到awk 代码后面的变量。只要您不需要 BEGIN 块中的变量,这将正常工作:

variable="line one\nline two"
echo "input data" | awk 'print var' var="$variable"
or
awk 'print var' var="$variable" file
添加多个变量:

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

这样我们还可以为每个文件设置不同的字段分隔符FS

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

代码块后的变量不适用于BEGIN 块:

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


这里字符串

也可以使用支持它们的 shell(包括 Bash)中的 here-string 将变量添加到 awk

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

这与:

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

附:这会将变量视为文件输入。


ENVIRON输入

正如 TrueY 所写,您可以使用ENVIRON 打印环境变量。 在运行 AWK 之前设置一个变量,你可以这样打印出来:

X=MyVar
awk 'BEGINprint ENVIRON["X"],ENVIRON["SHELL"]'
MyVar /bin/bash

ARGV输入

正如 Steven Penny 所写,您可以使用 ARGV 将数据导入 awk:

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

要将数据放入代码本身,而不仅仅是 BEGIN:

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

代码中的变量:谨慎使用

您可以在awk 代码中使用变量,但它很混乱且难以阅读,而且正如Charles Duffy 所指出的,此版本也可能是代码注入的受害者。如果有人在变量中添加了坏东西,它将作为awk 代码的一部分执行。

这通过在代码中提取变量来工作,因此它成为它的一部分。

如果你想创建一个使用变量动态改变的awk,你可以这样做,但不要将它用于普通变量。

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

以下是代码注入示例:

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

您可以通过这种方式向awk 添加大量命令。甚至使用无效的命令使其崩溃。

不过,这种方法的一个有效用途是当您想将符号传递给 awk 以应用于某些输入时,例如一个简单的计算器:

$ calc()  awk -v x="$1" -v z="$3" 'BEGIN print x '"$2"' z '; 

$ calc 2.7 '+' 3.4
6.1

$ calc 2.7 '*' 3.4
9.18

没有办法使用填充了 shell 变量值的 awk 变量来做到这一点,您需要在 awk 解释它之前扩展 shell 变量以成为 awk 脚本文本的一部分。


额外信息:

双引号的使用

双引号变量"$variable"总是好的 如果没有,多行将被添加为一个长的单行。

例子:

var="Line one
This is line two"

echo $var
Line one This is line two

echo "$var"
Line one
This is line two

其他不带双引号的错误:

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

并且用单引号,它不会扩展变量的值:

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

更多关于 AWK 和变量的信息

Read this faq.

【讨论】:

“混乱且难以阅读”在直接将字符串替换为 awk 代码时忽略了代码注入更重要的安全问题。 阅读上面的答案我可以运行我的脚本没有错误,但它不能完成这项工作:awk -v repo="$1" -v tag="$2" 'sub(/image: registryabx.azurecr.io\/print repo:([a-z0-9]+)$/,"image: registryabc.azurecr.io/print repo:print tag");1'。 /services/appscompose.yaml >> newcompose.yaml。是因为嵌套括号? @DarionBadlydone 试试这个awk -v repo="$1" -v tag="$2" 'BEGIN print "repo="repo,"tag="tag'。它将查看是否打印变量。如果您无法弄清楚,请发布自己的问题。 @Jotne 是的,它打印了值,所以我尝试了这种方式: awk -v repo="$1" -v tag="$2" 'print "sub(/image: registryabc.azurecr .io/"repo":([a-z0-9]+)$/,\"image: registryabc.azurecr.io/"repo":"tag"\");1"' ./services/ appscompose.yaml >> newcompose.yaml 但不能正常工作。它用打印的命令替换源文件的每一行 我强烈反对 -v 是“最好、最便携的方式”。 awk -v a=b cmds path1 path2 (几乎)等价于awk cmds a=b path1 path2,但是没有很好的方法可以使用-v 来模拟awk cmds path1 a=b path2 在参数中定义变量是一种非常有用的技术,它同样具有可移植性,我认为它是“更好”。【参考方案2】:

好老的ENVIRON awk 内置hash好像根本没提。其用法示例:

$ X=Solaris awk 'BEGINprint ENVIRON["X"], ENVIRON["TERM"]'
Solaris rxvt

【讨论】:

这是一个很好的建议,因为它逐字传递数据。 -v 在值包含反斜杠时不起作用。 @thatotherguy 我不知道!我认为如果我使用awk -v x='\c\d' ...,那么它将被正确使用。但是当x 被打印时awk 丢弃著名的:awk: warning: escape sequence '\c' treated as plain 'c' 错误消息...谢谢! 它确实可以正常工作 - 在这种情况下正常意味着扩展转义序列,因为 -v 就是这样设计的,因此您可以在变量中使用 \t 并让它与数据,例如。如果这不是您想要的行为,那么您不要使用-v,而是使用ARGV[]ENVIRON[]【参考方案3】:

根据您希望如何处理 shell 变量中的反斜杠(avar 是一个 awk 变量,svar 是一个 shell 变量),使用其中任何一个:

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

有关详细信息和其他选项,请参阅http://cfajohnson.com/shell/cus-faq-2.html#Q24。上面的第一种方法几乎总是您的最佳选择,并且具有最明显的语义。

【讨论】:

【参考方案4】:

您可以使用变量名 (v) 和环境变量 ("$v") 的值 (=) 传入 command-line option -v

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

或者说得更清楚(vs 少得多):

% environment_variable=123test
% awk -vawk_variable="$environment_variable" 'BEGIN  print awk_variable '
123test

【讨论】:

【参考方案5】:

您可以使用 ARGV:

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

注意,如果你要继续进入身体,你需要调整 ARGC:

awk 'BEGIN ARGC-- print ARGV[2], $0' file "$v"

【讨论】:

【参考方案6】:

我刚刚更改了@Jotne 对“for 循环”的回答。

for i in `seq 11 20`; do host myserver-$i | awk -v i="$i" 'print "myserver-"i" " $4'; done

【讨论】:

这似乎只是另一个说明如何使用 Awk 的 -v 选项,在许多现有答案中已经提到过。如果你想展示如何在循环中运行 Awk,那真的是一个不同的问题。【参考方案7】:

我必须在日志文件行的开头插入日期,如下所示:

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

可以重定向到另一个文件保存

【讨论】:

双引号 - 单引号 - 双引号正是我需要的。 这已经在接受的答案中提到,由于代码注入漏洞,您不应该使用这种方法。所以这里的信息是多余的(已经在接受的答案中描述过),并且不完整(没有提到这种方法的问题)。【参考方案8】:

专业提示

创建一个处理这个问题的函数会很方便,这样您就不必每次都键入所有内容。使用我们得到的所选解决方案...

awk_switch_columns() 
     cat < /dev/stdin | awk -v a="$1" -v b="$2" "  t = \$a; \$a = \$b; \$b = t; print;  "

并将其用作...

echo 'a b c d' | awk_switch_columns 2 4

Output:
a d c b

【讨论】:

【参考方案9】:

示例:

in.txt:

foo
bar

变量:

var=$(awk 'print $1' in.txt) 

命令:

echo -e "$var" > out.txt

out.txt

foo
bar

另一个:

in.txt

foo,aaa
bar,bbb

变量:

var=$(awk -F "," 'print $1' in.txt) 

out.txt

foo
bar

或:

var=$(awk -F "," 'print $2' in.txt) 

out.txt

aaa
bbb

【讨论】:

以上是关于如何在 awk 脚本中使用 shell 变量?的主要内容,如果未能解决你的问题,请参考以下文章

awk中使用$0与shell脚本$1有冲突怎么办

如何将awk中一个变量的值传递给shell中的变量

[Linux Shell学习系列十四]sed和awk-6.awk与Shell

编shell脚本遇到一个问题,awk能切割掉最后一个字段的值嘛?

是否可以将脚本传递给shell变量中的awk?

shell编程