我需要使用 (*) 处理大量文件的一个大子集,AWK
以便它在文件中累积一组变量。
AWK
使用文件通配符传递多个文件名的直接方法对于小型文件集来说效果很好,但"Argument list too long"
在使用生产规模的文件集运行时会出现预期的结果。
解决此类问题的最佳实践方法是什么?
一些细节:
整套文件为20-50K文件;目前,单次运行的子集为 5-10K(但如果可以轻松扩展,那就太好了)
我需要计算一组文件中每个单词的出现次数,为每个文件提供运行时定义的权重:同一文件中的每个单词获得相同的权重,但不同文件中出现的相同单词获得不同的权重。然后为每个单词添加文件权重。
因此,将文件集分割成更小的子集意味着聚合中间结果。它看起来不太优雅,并且需要在加入多个中间文件时添加浮点,这使得整个过程的可读性和直观性更差。
我能想到的另一种方法是提供&
awk
的输出。我不喜欢的是牺牲/的可读性并解决文件之间的某些分隔符的解析以重置文件特定的权重、计数器和数组。find
cat
BEGINFILE
ENDFILE
当前文件夹中要处理的文件子集作为单独的文件 A 提供;在
BEGINFILE
我的部分中跳过我不需要的文件- 每个文件 X 的权重源自该文件与参考文件 B 的组合;基本上它是 X 和 B 之间共有的单词与 X 中单词数量的比率
- 将文件权重计算与跨文件聚合分开意味着二读取传递了数十 GB,我想避免这种情况
(*) 或者也许AWK
不是进行此类处理的最佳工具?如果是这样,您会推荐什么替代方案?
答案1
如果参数太多,您将必须自己打开并处理文件。使用 awk,无需使用任何扩展,您就可以使用它(与 Jeff 的答案相同的想法):
awk '{ filename = $0; while(getline < filename > 0) { print $0; }}'
例如,结合find
命令来查找您需要的文件:
find /etc/ -maxdepth 1 -type f -perm -444 -size 1 | \
awk '{ filename = $0; while(getline < filename > 0) { print filename ":" $0; }}'
此外,根据 awk 的版本,可以推送更多文件进行处理如此处记录的。
程序可以改变 ARGC 和 ARGV 的元素。每次 awk 到达输入文件的末尾时,它都会使用 ARGV 的下一个元素作为下一个输入文件的名称。通过在那里存储不同的字符串,程序可以更改读取的文件。使用“-”代表标准输入。存储附加元素并递增 ARGC 会导致读取附加文件。
用一个例子来说明:
find /etc/ -maxdepth 1 -type f -perm -444 -size 1 | \
awk '
# When reading from STDIN, assume it is a list of files to read
FILENAME == "-" { ARGV[ARGC] = $0; ARGC += 1 }
# When not reading STDIN, it is a file to process
FILENAME != "-" { print "---", FILENAME ":" FNR ":" $0; }
# These will run after every file, including STDIN, hence the check
BEGINFILE { if (FILENAME != "-") { print ">>>", FILENAME; } }
ENDFILE { if (FILENAME != "-") { print "<<<", FILENAME, FNR, "lines"; } }'
答案2
如果您的文件名不包含引号或空格,一种选择是将它们堆积在一起cat
:
printf '%s ' * | xargs cat | awk ...
printf
上面的代码只是通过使用内置 ( ) 打印每个文件名来解决“参数列表太长”错误,然后将其发送到xargs
,这会将文件名分成多个批次,然后发送到cat
,然后将其输出发送到到awk
。
如果您有可用的 GNU awk (gawk)4.1 或以上版本,其中引入了动态模块加载,它包含一个可以读取目录本身的扩展,从而绕过了该问题。
这是一个示例 gawk 程序,它将打开并读取您传递给它的任何目录中的文件;然后,您必须显式地读取您感兴趣的每个文件。这样做的好处是您有一个可以读取每个文件的 (GNU) awk 程序。
@load "readdir"
@load "filefuncs"
BEGIN { FS = "/" }
{
result = stat($2, statdata)
if (statdata["type"] != "file")
next
FS = " "
while(getline < statdata["name"] > 0) {
#print $1
}
FS = "/"
}
该脚本的主循环遍历命令行上给出的每个参数,并尝试将其作为目录打开。结果字段是:
- $1 = 索引节点号
- $2 = 文件名
- $3 = 文件类型
然后我们使用 filefuncs 函数stat
来检查文件的类型。如果它不是普通文件,我们将跳过它。否则,我们设置FS
回正常值并用于getline
读取文件。处理完每个文件后,我们将 FS 重置回,/
以便它可以从readdir
.