如果文件名包含 =,为什么 awk 会停止并等待以及如何解决这个问题?

如果文件名包含 =,为什么 awk 会停止并等待以及如何解决这个问题?
awk 'processing_script_here' my=file.txt

似乎无限期地停下来等待......
这里发生了什么事以及如何让它发挥作用?

答案1

作为克里斯说,形式的参数variablename=anything被视为变量赋值(在处理参数时执行,而不是在语句-v var=value之​​前执行的(较新的)变量赋值BEGIN)而不是输入文件名。

这在以下方面很有用:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

您可以在其中指定不同的FS/RS每个文件。它也常用于:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

这是更安全的版本:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

file1(如果为空则不起作用)

=但是,当您的文件名称中包含字符时,就会出现问题。

=现在,只有当第一个剩下的内容是有效的变量名时,这才是问题awk

中有效变量名的构成awk比 中更严格sh

POSIX 要求它类似于:

[_a-zA-Z][_a-zA-Z0-9]*

仅使用可移植字符集的字符。然而,/usr/xpg4/bin/awkSolaris 11 至少在这方面不兼容,并且允许变量名称中使用语言环境中的任何字母字符,而不仅仅是 a-zA-Z。

x+y=foo因此,像or =baror 这样的参数./foo=bar仍然被视为输入文件名,而不是赋值,因为第一个参数剩下的=不是有效的变量名。诸如“可能”或“不”之类的参数Stéphane=Chazelas.txt,取决于awk实现和区域设置。

这就是为什么使用 awk 时,建议使用:

awk '...' ./*.txt

代替

awk '...' *.txt

例如,如果您不能保证文件名txt不包含=字符,则可以避免出现问题。

另外,请注意,如果您使用以下参数,则可能会将类似的参数-vfoo=bar.txt视为选项:

awk -f file.awk -vfoo=bar.txt

(也适用于1.28.0 之前的 busybox 版本,请awk '{code}' -vfoo=bar.txt参阅awk相应的错误报告)。

再次强调,使用./*.txt可以解决这个问题(使用前缀./也有助于调用一个文件,-否则它awk会理解为含义标准输入反而)。

这也是为什么

#! /usr/bin/awk -f

shebangs 真的不起作用。虽然这些var=value可以通过以下方法解决定影语句中的值ARGV(添加./前缀)BEGIN

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

这对选项没有帮助,因为这些选项是由脚本看到的awk,而不是awk脚本看到的。

使用该./前缀的一个潜在的外观问题是它以 结尾,但如果您不想要它,FILENAME您可以随时使用它来删除它。substr(FILENAME, 3)

GNU 的实现awk通过其选项修复了所有这些问题-E

之后-E,gawk 仅期望脚本的路径awk(其中-still 表示 stdin),然后仅期望输入文件路径的列表(并且甚至不-进行特殊处理)。

它专为:

#! /usr/bin/gawk -E

shebangs,其中参数列表始终是输入文件(请注意,您仍然可以自由地ARGV在语句中编辑该列表BEGIN)。

您还可以将其用作:

gawk -e '...awk code here...' -E /dev/null *.txt

我们使用-E空脚本 ( /dev/null) 只是为了确保*.txt后面的脚本始终被视为输入文件,即使它们包含=字符。

答案2

在 awk 的大多数版本中,要执行的程序后面的参数是:

  1. 一份文件
  2. 表格的分配x=y

由于您的文件名被解释为情况 #2,因此 awk 仍在等待在 stdin 上读取某些内容(因为它没有感知到已传递任何文件名)。

可移植的是,这种行为是记录在 POSIX 中:

以下两种类型的参数可以混合使用:

  • 文件:包含要读取的输入的文件的路径名,该输入与程序中的模式集进行匹配。如果未指定文件操作数,或者文件操作数为“-”,则应使用标准输入。
  • 赋值:以下划线或可移植字符集中的字母字符开头的操作数(请参阅 IEEE Std 1003.1-2001 的基本定义卷中的表,第 6.1 节,可移植字符集),后跟一系列下划线、数字、和可移植字符集中的字母,后跟“=”字符,应指定变量赋值而不是路径名。

因此,您有几个可移植的选择(#1 可能是干扰最小的):

  1. 使用awk ... ./my=file,它回避了这一点,因为.它不是“可移植字符集中的下划线或字母字符”。
  2. 使用 将该文件放在标准输入上awk ... < my=file。但是,这对于多个文件来说效果不佳。
  3. 临时创建一个到该文件的硬链接,然后使用它。您可以执行类似的操作ln my=file my_file,然后my_file正常使用。不会执行任何复制,并且两个文件将由相同的数据和索引节点元数据支持。使用它后,删除创建的链接是安全的,因为对 inode 的引用数量仍然大于 0。

答案3

去引用gawk 文档(注意添加强调):

命令行上的任何其他参数通常被视为要按指定顺序处理的输入文件。然而,形式为 var=value 的参数将 value 赋给变量 var — 它根本不指定文件。

为什么命令会停止并等待?因为在形式上awk 'processing_script_here' my=file.txt 没有指定文件根据上面的定义 -my=file.txt被解释为变量赋值,如果没有定义文件,awk将读取 stdin (也可以明显看出strace,此类命令中的 awk 正在等待read(0,'...)系统调用。

这也记录在POSIX awk 规范,参见操作数部分和作业其中一部分)

变量分配很明显,因为/etc/passwd 中的每一行都会打印 的awk '{print foo}' foo=bar /etc/passwd值。但是foo指定./foo=bar路径或完整路径确实有效。

请注意,运行straceawk '1' foo=bar检查cat foo=bar显示这是 awk 特定的问题,并且 execve 确实将文件名显示为传递的参数,因此在这种情况下 shell 与环境变量分配无关。

另外,请注意,这awk '...script...' foo=bar不会导致 shell 创建环境变量,因为环境变量分配应该在命令之前才能生效。看POSIX Shell 语法规则,第 7 点。此外,这可以通过以下方式验证awk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

相关内容