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

388

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

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

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

然后尝试了awk:

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

为什么会有差异?

最后我尝试了这个:

$ 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 

我对此感到困惑。


2
我喜欢下面展示的-v,但这真的是一个很好的思考如何保护shell中的东西的练习。在处理过程中,我的第一次尝试是在空格和美元符号上使用反斜杠。不用说,这里的例子非常值得我花时间去学习。 - Chris
相关:awk 中单引号和双引号的区别 - codeforester
2
如果你的awk搜索需要使用正则表达式,就不能把/var/放进去。相反地,使用波浪号:awk -v var="$var" '$0 ~ var' - Noam Manos
@NoamManos,为什么无法在由“//”分隔的正则表达式中使用变量?我已经阅读了很多信息(顺便说一下,awk手册非常好),已经几个小时了,我已经有点不知所措了,如果这很容易找到,请原谅。 - Kiteloopdesign
1
@Kiteloopdesign 因为 /.../ 分隔符表示字面意义的正则表达式,所以其中不会有任何扩展。如果您不想使用字面意义的正则表达式,则不要使用 /.../ 分隔符,而是使用 "..." 和/或变量来创建动态正则表达式。 - Ed Morton
7个回答

646
#将shell变量传递给awk可以用几种方法完成。其中一些方法比其他方法更好。这应该包括其中的大部分。如果您有评论,请在下方留言。 v1.5

使用 -v 选项(最佳方式,最具可移植性)

使用 -v 选项:(附注:在 -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[]访问来解决。

附注 如果您的分隔符中有竖线或其他正则表达式元字符,例如|?(等,它们必须进行双重转义。例如,3个竖线|||变成-F'\\|\\|\\|'。您也可以使用-F"[|][|][|]"

从程序/函数中获取数据到awk的示例(这里使用了日期)

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

测试将shell变量的内容作为正则表达式的示例:
awk -v var="$variable" '$0 ~ var{print "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 '一些代码' FS=',' file1.txt FS=';' file2.ext

  • 代码块后面的变量在BEGIN块中无效:

echo "输入数据" | awk 'BEGIN {print var}' var="${variable}"


Here-string

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

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

这与以下内容相同:
printf '%s' "$variable" | awk '{print $0}'

P.S. 这将变量视为文件输入。

ENVIRON 输入

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

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

或者对于一个未导出的变量:
x=MyVar
x="$x" awk 'BEGIN{print 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 'BEGIN{var=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变量来实现这个,你需要让shell变量在awk解释之前扩展成awk脚本的一部分。请参考下面Ed M.的评论。

额外信息:

使用双引号

在变量"$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和变量的更多信息

阅读此常见问题解答


2
我强烈反对认为-v是“最佳、最便携的方式”。 awk -v a=b cmds path1 path2awk cmds a=b path1 path2(几乎)等效,但没有很好的方法使用-v来模拟awk cmds path1 a=b path2。在参数中定义变量是一种非常有用的技术,同样具有可移植性,并且我会认为它更好。 - William Pursell
@WilliamPursell 当您在args列表中定义变量时,a)它们不会在“BEGIN”部分中设置,b)它们与文件名交错在ARGV[]中,因此使循环文件名更加困难,比较当前的FILENAME与ARGV[]位置,例如使用FILENAME==ARGV[1]而不是NR==FNR以避免多输入文件脚本中的空输入文件问题。在我看来,唯一需要这样做的时间是当您需要在文件之间更改变量值(例如FS)时,否则请使用-vENVIRON[]以最直观的方式使用变量。 - Ed Morton
1
关于“没有好的方法使用-v来模拟awk cmds path1 a=b path2” - 你也可以声称使用这种方法来模拟“awk -v a=b cmds path1 path2”没有好的方法,因为它们有不同的语义。在我看来,使用“awk -v a=b cmds path1 path2”来模拟“awk cmds path1 a=b path2”比另一种方式更容易,因为在第一种方式中,BEGIN部分中根本没有可用的“a”,而在第二种方式中,在BEGIN部分中保存/清除/设置文件之间的“a”非常容易。 - Ed Morton

34

4
这是一个好建议,因为它直接传递数据。当值包含反斜杠时,-v 不起作用。 - that other guy
2
@thatotherguy我不知道!我以为如果我使用awk -v x='\c\d' ...,那么它会被正确使用。但是当打印x时,[tag:awk]会出现著名的错误消息:awk:警告:转义序列'\c'被视为普通'c'...谢谢! - TrueY
1
它可以正常工作 - 在这个上下文中,“properly”意味着扩展转义序列,因为-v是设计成这样工作的,所以您可以在变量中使用\t并将其与数据中的字面制表符匹配,例如。如果这不是您想要的行为,则不使用-v,而是使用ARGV[]ENVIRON[] - Ed Morton

10

您可以通过在命令行选项中传递变量名(v)和环境变量的值(=)来使用命令行选项 -v

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

或者说更清晰一点(使用更少的v):

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

这只是重申了被接受的答案的一部分,但由于-vv=之间没有空格,它只适用于某些awk。 - Ed Morton

6
你可以使用ARGV:
v=123test
awk 'BEGIN {print ARGV[1]}' "$v"

请注意,如果您要继续进入主题,您需要调整ARGC:
awk 'BEGIN {ARGC--} {print ARGV[2], $0}' file "$v"

这只是重申了已接受的答案的一部分,并且只减少 ARGC 而不清除 ARGV[] 中其插槽,可能会因人而异。 - Ed Morton

1
我刚刚修改了@Jotne关于“for循环”的答案。
for i in `seq 11 20`; do host myserver-$i | awk -v i="$i" '{print "myserver-"i" " $4}'; done

2
这似乎只是另一个示例,说明如何使用Awk的-v选项,这在许多现有答案中已经提到。如果你想展示如何在循环中运行Awk,那就是一个完全不同的问题了。 - tripleee

0

我需要在日志文件的每行开头插入日期,可以像下面这样完成:

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

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


双引号-单引号-双引号正是我需要的,让我的代码工作了。 - user53029
3
这已经在被接受的答案中提到了,因为存在代码注入漏洞,所以不应该使用这种方法。因此,这里的信息是多余的(已经在被接受的答案中描述),并且不完整(没有提到这种方法的问题)。 - Jason S

-1

专业提示

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

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

请参见 https://porkmail.org/era/unix/award 中的UUOC。此外,使用单引号而不是双引号来包围您的awk脚本(默认情况下应始终如此),然后您就不必在其中转义$符号,因为您不会邀请shell在awk看到它之前对其进行解释。不明白为什么您在这个答案的顶部放置了大而粗的“专业提示”,大多数其他答案都更好,这并没有为接受的答案增加任何价值,它只是在一个特定的上下文中使用它。 - Ed Morton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接