sed
诸如、awk
或perl -n
处理其输入之类的工具记录一次,记录存在线默认情况下。
有些,例如awk
with RS
、GNU sed
with-z
或perl
with-0ooo
可以通过选择不同的记录分隔符来更改记录类型。
perl -n
可以使整个输入(传递多个文件时的每个单独文件)成为一个记录与-0777
选项(或-0
后跟任何大于 0377 的八进制数,777 是规范的)。这就是他们所说的吸食模式。
awk
可以用'sRS
或任何其他机制完成类似的事情吗?其中awk
处理每个文件内容按顺序排列,而不是按顺序排列线每个文件的?
答案1
您可以采取不同的方法,具体取决于是否awk
将其RS
视为单个字符(如传统awk
实现那样)或正则表达式(如gawk
或mawk
do)。空文件也很难被考虑,因为awk
往往会跳过它们。
gawk
,mawk
或其他可以是正则表达式的awk
实现RS
。
在这些实现中(对于mawk
,请注意某些操作系统(例如 Debian)发布了非常旧的版本,而不是由 @ThomasDickey 维护的现代版本),如果RS
包含单个字符,则记录分隔符为该字符,或为空awk
时进入段落模式RS
,RS
否则视为正则表达式。
解决方案是使用不可能匹配的正则表达式。有些像x^
或$x
(x
开始之前或结束之后)。然而,有些(特别是gawk
)比其他更贵。到目前为止,我发现这^$
是最有效的一种。它只能匹配空输入,但这样就没有任何东西可以匹配。
所以我们可以这样做:
awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...
但需要注意的是,它会跳过空文件(与 相反perl -0777 -n
)。 GNU 可以awk
通过将代码放在ENDFILE
语句中来解决这个问题。但我们还需要$0
在 BEGINFILE 语句中重置,否则在处理空文件后它不会被重置:
gawk -v RS='^$' '
BEGINFILE{$0 = ""}
ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...
传统awk
实现、POSIXawk
其中,RS
只是一个字符,它们没有BEGINFILE
/ ENDFILE
,它们没有RT
变量,它们通常也无法处理 NUL 字符。
您可能会认为 usingRS='\0'
可以工作,因为无论如何它们都无法处理包含 NUL 字节的输入,但不,RS='\0'
在传统实现中,它被视为RS=
,这是段落模式。
一种解决方案是使用不太可能在输入中找到的字符,例如\1
。在多字节字符区域设置中,您甚至可以将其设置为不太可能出现的字节序列,因为它们形成未分配的字符或非字符,例如$'\U10FFFE'
UTF-8 区域设置中的字符。但并不是万无一失,而且空文件也有问题。
另一种解决方案是将整个输入存储在一个变量中,并在最后的 END 语句中对其进行处理。这意味着您一次只能处理一个文件:
awk '{content = content $0 RS}
END{$0 = content
printf "%s: <%s>\n", FILENAME, $0
}' file
这相当于sed
:
sed '
:1
$!{
N;b1
}
...' file1
这种方法的另一个问题是,如果文件不是以换行符结尾(并且不是空),则仍然会在$0
末尾任意添加一个(使用gawk
,您可以通过使用RT
而不是RS
在上面的代码)。一个优点是您确实在NR
/中记录了文件的行数FNR
。
要一次处理多个文件,一种方法是在一条语句中手动读取所有文件BEGIN
(这里假设 POSIX awk
,而不是 /bin/awk
带有 70 年代 API 的 Solaris ):
awk -- '
BEGIN {
for (i = 1; i < ARGC; i++) {
FILENAME = ARGV[i]
$0 = ""
while ((getline line < FILENAME) > 0)
$0 = $0 line "\n"
# actual processing here, example:
print i". "FILENAME" has "NF" fields and "length()" characters."
}
}' *.txt
关于尾随换行符的相同警告。该文件的优点是能够处理包含=
字符的文件名。