![AWK:防止传递给外部实用程序的参数进行字段分割](https://linux22.com/image/171851/AWK%EF%BC%9A%E9%98%B2%E6%AD%A2%E4%BC%A0%E9%80%92%E7%BB%99%E5%A4%96%E9%83%A8%E5%AE%9E%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%8F%82%E6%95%B0%E8%BF%9B%E8%A1%8C%E5%AD%97%E6%AE%B5%E5%88%86%E5%89%B2.png)
在 AWK 脚本内部,我可以将变量作为参数传递给外部实用程序:
awk 'BEGIN {
filename = "path_to_file_without_space"
"file " filename | getline
print $0
}'
但如果变量包含空格,
awk 'BEGIN {
filename = "path to file with spaces"
"file " filename | getline
print $0
}'
我收到错误
file: cannot open `path' (No such file or directory)
建议在空格上分割参数,这与 shell 在空格上分割未加引号的变量的方式非常相似。我想通过将 shell 的 IFS 设置为 null 来禁用 shell 字段分割,如下所示
"IFS= file " filename | getline
或者在运行 AWK 命令之前将 IFS 设置为 null,但这两个选项都没有任何区别。如何避免这种字段分裂?
答案1
您必须引用文件名:
awk 'BEGIN {
filename = "path to file with spaces"
"file \"" filename "\"" | getline
print
}'
或者,正如评论中所建议的,为了便于阅读,
awk 'BEGIN {
DQ = "\042" # double quote (ASCII octal 42)
filename = "path to file with spaces"
"file " DQ filename DQ | getline
print
}'
或者,假设这是一个更大awk
程序的一部分,
BEGIN {
SQ = "\047"
DQ = "\042"
}
BEGIN {
name = "filename with spaces"
cmd = sprintf("file %s%s%s", DQ, name, DQ)
cmd | getline
close(cmd)
print
}
也就是说,完成后关闭该命令以保存打开的文件句柄。在单独的块中设置方便的“常量” BEGIN
(这些块按顺序执行)。使用sprintf
到单独的变量中创建命令。 (这些东西中的大多数显然是针对更长或更复杂的awk
程序,需要提供一种可读的结构以便维护;人们也可以想象编写一个引用字符串的dquote()
and函数)squote()
“管道”的左侧将计算为文字字符串
file "path to file with spaces"
基本上, usingcmd | getline
使awk
调用sh -c
带有单个参数,即 string cmd
。因此,必须正确引用该字符串才能使用 执行sh -c
。
技术细节见POSIX标准:
expression | getline [var]
从命令输出通过管道传输的流中读取输入记录。如果当前没有打开流,则应创建该流并将 的值
expression
作为其命令名称。创建的流应等效于通过调用popen()
函数创建的流,其中表达式的值作为命令参数,值作为r
参数mode
。只要流保持打开状态,expression
计算结果为相同字符串值的后续调用就应从流中读取后续记录。流应保持打开状态,直到close
使用计算结果为相同字符串值的表达式调用函数为止。那时,流将被关闭,就像通过调用该pclose()
函数一样。如果var
省略,$0
则应NF
设置;否则,var
应设置,并且如果合适,应将其视为数字字符串(请参阅 awk 中的表达式)。
popen()
这里指的函数是C库popen()
函数。这安排了给定的字符串由 执行sh -c
。
system()
如果使用带空格的文件名执行命令,您将遇到完全相同的问题,但在这种情况下system()
,将调用 C 库的函数,该函数还调用sh -c
方式与 类似popen()
(但 I/O 流的管道不同)。
因此,如果使用单个参数调用,IFS
任何设置都无济于事sh -c
file path to file with spaces
答案2
请注意,对于任意文件名,空格是您最不用担心的。例如,考虑一个名为$(reboot)
or foo;reboot #whatever
or foo|reboot|bar
...的文件
awk
调用以解释其、、sh
中的命令行,因此,在根据任意输入构建命令行时,正确转义参数以避免命令注入漏洞至关重要。cmdline | getline
print | cmdline
system(cmdline)
在 shell 中引用是一件棘手的事情。 Shell 有大量不同的引用运算符 ( '...'
, "..."
, \
, $'...'
, $"..."
),但'...'
由于它们不会转义,因此可能不安全每一个字符(特别是,它们不会转义\
危险字符,因为它的编码也存在于某些字符集中其他字符的编码中)。
同样重要的是不要`...`
在 shell 代码中使用旧形式的命令替换,因为它们引入了另一级别的反斜杠处理。
假设您在环境变量中有任意文件名:
#! /bin/sh -
FILE="${1?No file provided}"
export FILE
awk -v q="'" '
function shquote(s) {
gsub(q, "&\"&\"&", s)
return q s q
}
BEGIN {
cmdline = "file -- " shquote(ENVIRON["FILE"])
if ((cmdline | getline) > 0)
print "The first line of \""cmdline"\" output was \""$0"\"."
else
print "Could not read a line from \""cmdline"\" output."
if (close(cmdline) != 0)
print cmdline" failed."
}'
上面,shquote()
采用一个字符串作为参数,并sh
通过将其括在单引号(最安全的引号)中来对其进行引号,但字符串本身中的单引号更改为'"'"'
,即结束'
,后跟一个'
引号 with ,"..."
后跟另一个'
重新打开的引号另一个单引号字符串。
您会注意到上面的一些其他可能的警告提示:
- 您需要 a
--
来确保您的文件名以-
. - 该命令的输出
file
不保证在一行上,特别是当文件名本身包含换行符时。毕竟,换行符与文件名中的任何字符一样有效。getline
只读取一条记录,默认记录为行。看awk 中的 Slurp 模式?有关如何读取整个输出的提示。 - 该输出也可能根本没有任何行。要从空的第一行看出这一点,您需要检查 的返回值
getline
。 - 最好检查命令的退出状态,并在需要时报告问题。这是通过查看返回的值来完成的
close()
。但请注意,不同的awk
实现对于该值如何编码退出状态存在差异。唯一的共同点是,当命令成功时(以 0 退出代码退出),该值为 0。