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

How do I use shell variables in an awk script?

提问人:hqjma 提问时间:9/29/2013 最后编辑:codeforesterhqjma 更新时间:8/19/2023 访问量:671351

问:

我找到了一些将外部 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 

我对此感到困惑。

bash shell awk

评论

2赞 Chris 12/21/2016
我喜欢如下所示的 -v,但这确实是思考如何保护东西免受外壳攻击的一个很好的练习。为了解决这个问题,我的第一个剪辑在空格和美元符号上使用反斜杠。毋庸置疑,这里的例子非常值得我花时间。
0赞 codeforester 5/12/2018
相关新闻: awk 中单引号和双引号的区别
2赞 Noam Manos 5/7/2020
如果你的 awk 搜索需要正则表达式,你不能把 .请改用波浪号:/var/awk -v var="$var" '$0 ~ var'
0赞 Kiteloopdesign 8/10/2022
@NoamManos,为什么不能在以“//”分隔的 reg 表达式中使用变量?几个小时以来,我一直在阅读很多信息(以及精湛的 awk 手册,顺便说一句),我已经有点不知所措了,所以如果这很容易找到,请道歉
1赞 Ed Morton 7/12/2023
@Kiteloopdesign因为分隔符表示字面上的正则表达式,因此它们内部没有任何扩展。如果您不想要文字正则表达式,则不要使用分隔符,请使用 and/或变量作为动态正则表达式。/...//.../"..."

答:

10赞 johnsyweb 9/29/2013 #1

您可以使用环境变量 () 的变量名称 () 和值 () 传入命令行选项-vv="${v}"

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

或者说得更清楚(用更少的 s):v

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

评论

0赞 Ed Morton 1/13/2023
这只是重申了部分已接受的答案,但由于 和 之间没有空格,因此仅适用于某些 awk。-vv=
647赞 Jotne 9/29/2013 #2

可以通过多种方式 #Getting shell 变量。有些比其他的更好。这应该涵盖其中的大部分。如果您有任何意见,请在下面留下。1.5 版awk


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

使用选项:(P.S. 在后面使用空格,否则它的便携性会降低。-v-vawk -v var=awk -vvar=)

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

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

如果您有多个变量:

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

警告。正如埃德·莫顿(Ed Morton)所写,转义序列将被解释,因此成为真实,而不是如果这是您搜索的内容。可以通过使用或访问它来解决\ttab\tENVIRON[]ARGV[]

聚苯乙烯如果您有竖线或其他正则表达式元字符作为分隔符等,则必须对它们进行双重转义。示例 3 竖条变为 .您也可以使用 .|?(|||-F'\\|\\|\\|'-F"[|][|][|]"

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

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

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

awk -v var="$variable" '$0 ~ var{print "found it"}'

代码块后的变量

在这里,我们在代码之后得到变量。只要您不需要块中的变量,就可以正常工作:awkBEGIN

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}'

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


ENVIRON输入

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

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 所写,您可以使用以下方法将数据导入 awk:ARGV

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

代码中的变量:谨慎使用

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

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

如果你想使一个随着变量的使用而动态变化,你可以这样做,但不要将其用于普通变量。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 脚本文本的一部分。(见下面 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赞 William Pursell 2/9/2021
我强烈不同意这是“最好、最便携的方式”。 (几乎)等同于 ,但是没有好的方法来模拟 在参数中定义变量是一种非常有用的技术,它同样具有可移植性,我认为它“更好”。-vawk -v a=b cmds path1 path2awk cmds a=b path1 path2-vawk cmds path1 a=b path2
0赞 Ed Morton 1/12/2023
@WilliamPursell当您在 args 列表中的文件名中定义变量时,a) 它们没有在部分中设置,b) 它们与文件名交错,因此更难循环文件名,将当前与位置进行比较,例如使用而不是避免多输入文件脚本中的空输入文件问题。恕我直言,唯一需要这样做的时间是当您需要在文件之间更改变量的值(例如)时,否则使用或最直观地使用变量。BEGINARGV[]FILENAMEARGV[]FILENAME==ARGV[1]NR==FNRFS-vENVIRON[]
1赞 Ed Morton 1/12/2023
关于 - 你也可以声称没有很好的方法来使用这种方法来模拟,因为它们只是有不同的语义。恕我直言,它比相反的方式更容易模仿,因为在第一种方式的 BEGIN 部分中根本不可用,并且在 BEGIN 部分中很容易以第二种方式在文件之间保存/清除/设置它。there is no good way to use -v to emulate awk cmds path1 a=b path2awk -v a=b cmds path1 path2awk cmds path1 a=b path2awk -v a=b cmds path1 path2a
34赞 TrueY 11/6/2014 #3

似乎根本没有提到旧的 内置哈希。其用法示例:ENVIRON

$ X=Solaris awk 'BEGIN{print ENVIRON["X"], ENVIRON["TERM"]}'
Solaris rxvt

评论

4赞 that other guy 2/24/2016
这是一个很好的建议,因为它会逐字传递数据。 当值包含反斜杠时不起作用。-v
2赞 TrueY 2/24/2016
@thatotherguy我不知道!我以为如果我使用,那么它会被正确使用。但是当打印 awk 时会掉出著名的:错误消息......谢谢!awk -v x='\c\d' ...xawk: warning: escape sequence '\c' treated as plain 'c'
1赞 Ed Morton 7/7/2019
它确实可以正常工作 - 在这种情况下,正确地意味着扩展转义序列,因为这就是设计工作的方式,因此您可以在变量中使用并使其与数据中的文字选项卡匹配,例如。如果这不是你想要的行为,那么你就不使用你使用或.-v\t-vARGV[]ENVIRON[]
0赞 Sina 3/2/2016 #4

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

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

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

评论

0赞 user53029 7/21/2016
双引号 - 单引号 - 双引号正是我工作所需要的。
3赞 Jason S 10/12/2016
这已在接受的答案中提到,由于代码注入漏洞,您不应该使用这种方法。所以这里的信息是多余的(已经在接受的答案中描述过),而且不完整(没有提到这种方法的问题)。
6赞 Zombo 1/15/2017 #5

您可以使用 ARGV:

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

请注意,如果您要继续进入身体,则需要进行调整 ARGC:

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

评论

0赞 Ed Morton 1/13/2023
这只是重申了部分已接受的答案和 YMMV,只是减少了 ARGC 而不清除它在 ARGV[] 中的插槽。
1赞 edib 4/10/2018 #6

我只是将@Jotne的答案更改为“for 循环”。

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

评论

2赞 tripleee 7/7/2019
这似乎只是如何使用 Awk 选项的另一种说明,该选项在许多现有答案中已经提到过。如果你想展示如何在循环中运行 Awk,那真的是一个不同的问题。-v
-1赞 ibitebyt3s 4/18/2021 #7

专业提示

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

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

评论

0赞 Ed Morton 1/13/2023
请参阅 porkmail.org/era/unix/award 中的 UUOC。另外 - 在你的 awk 脚本周围使用单引号而不是双引号(默认情况下你总是应该这样做),然后你就不必转义其中的 s,因为你不会在 awk 看到它之前邀请 shell 解释它。为什么你把大而粗的“专业提示”放在这个答案的顶部并不明显,大多数其他答案都更好,这不会为接受的答案增加任何价值,它只是在一个特定的上下文中使用它。$