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/awk
Solaris 11 至少在这方面不兼容,并且允许变量名称中使用语言环境中的任何字母字符,而不仅仅是 a-zA-Z。
x+y=foo
因此,像or =bar
or 这样的参数./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 的大多数版本中,要执行的程序后面的参数是:
- 一份文件
- 表格的分配
x=y
由于您的文件名被解释为情况 #2,因此 awk 仍在等待在 stdin 上读取某些内容(因为它没有感知到已传递任何文件名)。
可移植的是,这种行为是记录在 POSIX 中:
以下两种类型的参数可以混合使用:
- 文件:包含要读取的输入的文件的路径名,该输入与程序中的模式集进行匹配。如果未指定文件操作数,或者文件操作数为“-”,则应使用标准输入。
- 赋值:以下划线或可移植字符集中的字母字符开头的操作数(请参阅 IEEE Std 1003.1-2001 的基本定义卷中的表,第 6.1 节,可移植字符集),后跟一系列下划线、数字、和可移植字符集中的字母,后跟“=”字符,应指定变量赋值而不是路径名。
因此,您有几个可移植的选择(#1 可能是干扰最小的):
- 使用
awk ... ./my=file
,它回避了这一点,因为.
它不是“可移植字符集中的下划线或字母字符”。 - 使用 将该文件放在标准输入上
awk ... < my=file
。但是,这对于多个文件来说效果不佳。 - 临时创建一个到该文件的硬链接,然后使用它。您可以执行类似的操作
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
路径或完整路径确实有效。
请注意,运行strace
并awk '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