don_crissti Asked: 2018-12-23 12:44:21 +0800 CST2018-12-23 12:44:21 +0800 CST 2018-12-23 12:44:21 +0800 CST 如果文件名包含 =,为什么 awk 会停止并等待,以及如何解决这个问题? 772 awk 'processing_script_here' my=file.txt 似乎停止并无限期地等待...... 这里发生了什么,我该如何让它工作? awk filenames 3 个回答 Voted Chris Down 2018-12-23T12:53:36+08:002018-12-23T12:53:36+08:00 在大多数版本的 awk 中,要执行的程序之后的参数是: 一份文件 表格的赋值x=y 由于您的文件名被解释为 case #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 元数据支持。使用它后,删除创建的链接是安全的,因为对 inode 的引用数仍将大于 0。 Best Answer Stéphane Chazelas 2018-12-23T14:34:03+08:002018-12-23T14:34:03+08:00 正如Chris 所说,表单的参数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变量名时,这才是一个问题。 构成有效变量名的 inawk比 in 更严格sh。 POSIX 要求它类似于: [_a-zA-Z][_a-zA-Z0-9]* 只有可移植字符集的字符。但是,/usr/xpg4/bin/awkSolaris 11 至少在这方面不兼容,并且允许在变量名中使用语言环境中的任何字母字符,而不仅仅是 a-zA-Z。 因此,像x+y=fooor =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 之前awk '{code}' -vfoo=bar.txt的busybox 版本,请参阅相应的错误报告)。awk 同样, using./*.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脚本的路径(其中-仍然表示标准输入),然后只需要输入文件路径的列表(在那里,甚至-没有被特殊处理)。 它专为: #! /usr/bin/gawk -E shebangs,其中参数列表始终是输入文件(请注意,您仍然可以在语句中自由编辑该ARGV列表)。BEGIN 您还可以将其用作: gawk -e '...awk code here...' -E /dev/null *.txt 我们使用-E空脚本 ( /dev/null) 只是为了确保*.txt之后的那些始终被视为输入文件,即使它们包含=字符。 Sergiy Kolodyazhnyy 2018-12-23T14:11:40+08:002018-12-23T14:11:40+08:00 引用gawk 文档(添加注意重点): 命令行上的任何附加参数通常被视为要按指定顺序处理的输入文件。但是,具有 var=value 形式的参数将值 value 分配给变量 var——它根本不指定文件。 为什么命令停止并等待?因为在表格awk 'processing_script_here' my=file.txt 中没有由上述定义指定的文件 -my=file.txt被解释为变量赋值,如果没有定义文件awk将读取标准输入(也很明显,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 与 env 变量分配无关。 另外,请注意这awk '...script...' foo=bar不会导致shell创建环境变量,因为环境变量分配应该在命令之前生效。请参阅POSIX Shell Grammar Rules,第 7 点。此外,这可以通过以下方式验证awk '{print ENVIRON["foo"]}' foo=bar /etc/passwd
在大多数版本的 awk 中,要执行的程序之后的参数是:
x=y
由于您的文件名被解释为 case #2,awk 仍在等待在 stdin 上读取某些内容(因为它没有感知到已传递任何文件名)。
可移植地,这种行为记录在 POSIX 中:
因此,可移植地,您有几个选项(#1 可能是干扰最小的):
awk ... ./my=file
,它回避了这一点,因为.
它不是“可移植字符集中的下划线或字母字符”。awk ... < my=file
。但是,这不适用于多个文件。ln my=file my_file
,然后my_file
正常使用。不会执行复制,两个文件将由相同的数据和 inode 元数据支持。使用它后,删除创建的链接是安全的,因为对 inode 的引用数仍将大于 0。正如Chris 所说,表单的参数
variablename=anything
被视为变量赋值(在处理参数时执行,而不是在语句之-v var=value
前执行的(较新的)参数)而不是输入文件名。BEGIN
这在以下方面可能很有用:
您可以在其中指定不同的
FS
/RS
每个文件。它也常用于:哪个是更安全的版本:
file1
(如果为空则不起作用)但是,当您的文件名称中包含
=
字符时,就会出现问题。现在,只有当第一个剩下的
=
是一个有效的awk
变量名时,这才是一个问题。构成有效变量名的 in
awk
比 in 更严格sh
。POSIX 要求它类似于:
只有可移植字符集的字符。但是,
/usr/xpg4/bin/awk
Solaris 11 至少在这方面不兼容,并且允许在变量名中使用语言环境中的任何字母字符,而不仅仅是 a-zA-Z。因此,像
x+y=foo
or=bar
or之类的参数./foo=bar
仍被视为输入文件名,而不是赋值,因为第一个参数的剩余部分=
不是有效的变量名。类似的参数Stéphane=Chazelas.txt
可能会也可能不会,取决于awk
实现和语言环境。这就是为什么使用 awk,建议使用:
代替
例如,如果您不能保证
txt
文件名不包含=
字符,则可以避免该问题。另外,请注意,
-vfoo=bar.txt
如果您使用以下参数,可能会将类似的参数视为一个选项:(也适用于1.28.0 之前
awk '{code}' -vfoo=bar.txt
的busybox 版本,请参阅相应的错误报告)。awk
同样, using
./*.txt
可以解决这个问题(使用./
前缀也有助于一个名为的文件-
,否则它被awk
理解为意味着标准输入)。这也是为什么
shebangs并没有真正起作用。虽然
var=value
可以通过在语句中修复ARGV
值(添加./
前缀)来解决这些问题:BEGIN
这对选项没有帮助,因为那些是由脚本
awk
而不是awk
脚本看到的。使用该
./
前缀的一个潜在外观问题是它以 结尾,但如果您不想要它FILENAME
,您可以随时使用它来剥离它。substr(FILENAME, 3)
GNU 实现通过它的选项
awk
修复了所有这些问题。-E
之后
-E
,gawk 只需要awk
脚本的路径(其中-
仍然表示标准输入),然后只需要输入文件路径的列表(在那里,甚至-
没有被特殊处理)。它专为:
shebangs,其中参数列表始终是输入文件(请注意,您仍然可以在语句中自由编辑该
ARGV
列表)。BEGIN
您还可以将其用作:
我们使用
-E
空脚本 (/dev/null
) 只是为了确保*.txt
之后的那些始终被视为输入文件,即使它们包含=
字符。引用gawk 文档(添加注意重点):
为什么命令停止并等待?因为在表格
awk 'processing_script_here' my=file.txt
中没有由上述定义指定的文件 -my=file.txt
被解释为变量赋值,如果没有定义文件awk
将读取标准输入(也很明显,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 与 env 变量分配无关。另外,请注意这
awk '...script...' foo=bar
不会导致shell创建环境变量,因为环境变量分配应该在命令之前生效。请参阅POSIX Shell Grammar Rules,第 7 点。此外,这可以通过以下方式验证awk '{print ENVIRON["foo"]}' foo=bar /etc/passwd