AWK:防止传递给外部实用程序的参数进行字段分割

AWK:防止传递给外部实用程序的参数进行字段分割

在 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 #whateveror foo|reboot|bar...的文件

awk调用以解释其、、sh中的命令行,因此,在根据任意输入构建命令行时,正确转义参数以避免命令注入漏洞至关重要。cmdline | getlineprint | cmdlinesystem(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。

相关内容