将文件名添加到 xargs 和 awk 命令的输出
Add filename to output of an xargs and awk command
我有一个充满 .txt 文件的目录,每个文件都有两列和许多行 (>10000)。对于这些文件中的每一个,我试图在第二列中找到最大值,并将第一列和第二列中的相应条目打印到输出文件中。为此,我有一个有效的 awk 命令。
find ./ -name "*.txt" | xargs -I FILE awk '{if(max<){max=;datum=}}END{print datum, max}' FILE >> out.txt
不过,我也想用每对数字打印对应输入文件的名称。输出看起来像:
file1.txt datum1 max1
file2.txt datum2 max2
为此,我试图从这个类似的问题中汲取灵感:
add filename to beginning of file using find and sed,
但我无法完全找到可行的解决方案。到目前为止,我的最大努力看起来像这样
find ./ -name "*.txt" | xargs -I FILE echo FILE | awk '{if(max<){max=;datum=}}END{print datum, max}' FILE >> out.txt
但我收到错误消息:
awk: can't open file FILE
source line number 1
我尝试了各种其他方法,但可能有几个字符是不正确的:
(1)
find ./ -name "*.txt" | xargs -I FILE -c "echo FILE ; awk '{if(max<){max=;datum=}}END{print datum, max}' FILE" >> out.txt
(2)
find ./ -name "*.txt" -exec sh -c "echo {} && awk '{if(max<){max=;datum=}}END{print datum, max}' {}" \; >> out.txt
我不介意使用什么命令(xargs 或 exec 或其他),我只关心输出。
如果所有 .txt 文件都在当前目录中,请尝试 (GNU awk):
awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' *.txt
如果您想在当前目录及其所有子目录中搜索 .txt 文件,请尝试:
find . -name '*.txt' -exec awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' {} +
因为现代 find
有一个 -exec
动作,命令 xargs
很少再需要了。
工作原理
{if(max=="" || max<+0){max=;datum=}}
这将找到第 2 列的最大值并将其和对应的值保存在第 1 列中。
ENDFILE{print FILENAME, datum, max; max=""}
到达每个文件的末尾后,这将从具有最大列 2 的行打印文件名和列 1 和列 2。
此外,在每个文件的末尾,max
被重置为空字符串。
例子
考虑一个包含这三个文件的目录:
$ cat file1.txt
1 1
2 2
$ cat file2.txt
3 12
5 14
4 13
$ cat file3.txt
1 0
2 1
我们的命令产生:
$ awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' *.txt
file1.txt 2 2
file2.txt 5 14
file3.txt 2 1
BSD awk
如果我们不能使用 ENDFILE,请尝试:
$ awk 'FNR==1 && NR>1{print f, datum, max; max=""} max=="" || max<+0{max=;datum=;f=FILENAME} END{print f, datum, max}' *.txt
file1.txt 2 2
file2.txt 5 14
file3.txt 2 1
因为一个 awk 进程可以分析很多文件,所以这种方法应该很快。
FNR==1 && NR>1{print f, datum, max; max=""}
每次我们开始一个新文件时,我们都会打印前一个文件的最大值。
在awk中,FNR
是当前文件的行号,NR
是目前读取的总行数。当 FNR==1 && NR>1
时,这意味着我们至少完成了一个文件,我们开始下一个文件。
max=="" || max<+0{max=;datum=;f=FILENAME}
和之前一样,我们捕获第2列的最大值和第1列的相应数据。我们还将文件名记录为变量f
。
END{print f, datum, max}
读完最后一个文件后,我们打印它的最大行。
find . -name '*.txt' | xargs -n 1 -I FILE awk '(FNR==1) || (max<){max=;datum=} END{print FILENAME, datum, max}' FILE >> out.txt
find . -name '*.txt' -exec awk '(FNR==1) || (max<){max=;datum=} END{print FILENAME, datum, max}' {} \; >> out.txt
(由 OP 编辑打字错误)
如果您有 10,000 个文件,每个文件 100,000 行,如果您像这样为每个文件开始新的 awk
调用,您将等待很长时间,因为您将必须创建 10,000 个进程:
find . -name \*.txt -exec awk ....
我创建了一些测试文件,发现上面的过程在我的 iMac.
上只需要 5 多分钟
所以,我决定看看那些可爱的 Intel 内核和我为 Apple 支付的所有可爱的闪存盘或许可以使用 GNU Parallel.
基本上,它会 运行 与您的 CPU 拥有的内核一样多的并行作业 - 在一个体面的 Mac 上可能有 4 或 8 个,并且它可以用它提供给命令的参数:
parallel --tag -q awk 'BEGIN{max=;d=} >max {max=;d=} END{print d,max}' ::: *.txt
这产生了相同的结果,现在 运行s 在 1 分 22 秒内,将近 4 倍的加速,- 不错!但我们可以做得更好……正如上面所说,我们仍然为每个文件调用一个新的 awk
,因此 10,000 个 awks
,但并行地,一次调用 8 个。最好将 OS 允许的尽可能多的文件传递给 运行 的 8 个 awk
中的每一个。幸运的是,GNU Parallel 将通过 -X
选项计算出对我们来说有多少:
parallel -X -q gawk 'BEGINFILE{max=;d=} >max {max=;d=} ENDFILE{print FILENAME,d,max}' ::: *.txt
现在需要 49 秒,但请注意,我对 ENDFILE
/BEGINFILE
使用 gawk
而不是 --tag
选项,因为每个 awk 调用现在都在接收数百个文件,而不仅仅是一个。
GNU Parallel 和 gawk 可以很容易地安装在 Mac 上 homebrew.您只需转到 homebrew website 并将单行代码复制并粘贴到您的终端中。然后你在 macOS 上有一个合适的包管理器,并可以访问数以千计的优质、有用、管理良好的包。
安装 homebrew 后,您可以安装 GNU Parallel:
brew install parallel
并且您可以安装 gawk:
brew install gawk
如果您不需要包管理器,请注意 GNU Parallel 只是一个 Perl 脚本和 macOS 与 Perl 一起发布。因此,您也可以非常简单地安装它:
(wget -O - pi.dk/3 || curl pi.dk/3/ ) | bash
请注意,如果您的文件名超过 25 个字符,您将达到参数长度 262,144 个字符的限制,并收到一条错误消息,告诉您参数列表太长。如果发生这种情况,只需像这样在 stdin
上提供姓名:
find . -name \*.txt -print0 | parallel -0 -X -q gawk 'BEGINFILE{max=;d=} >max {max=;d=} ENDFILE{print FILENAME,d,max}'
我有一个充满 .txt 文件的目录,每个文件都有两列和许多行 (>10000)。对于这些文件中的每一个,我试图在第二列中找到最大值,并将第一列和第二列中的相应条目打印到输出文件中。为此,我有一个有效的 awk 命令。
find ./ -name "*.txt" | xargs -I FILE awk '{if(max<){max=;datum=}}END{print datum, max}' FILE >> out.txt
不过,我也想用每对数字打印对应输入文件的名称。输出看起来像:
file1.txt datum1 max1
file2.txt datum2 max2
为此,我试图从这个类似的问题中汲取灵感: add filename to beginning of file using find and sed, 但我无法完全找到可行的解决方案。到目前为止,我的最大努力看起来像这样
find ./ -name "*.txt" | xargs -I FILE echo FILE | awk '{if(max<){max=;datum=}}END{print datum, max}' FILE >> out.txt
但我收到错误消息:
awk: can't open file FILE
source line number 1
我尝试了各种其他方法,但可能有几个字符是不正确的:
(1)
find ./ -name "*.txt" | xargs -I FILE -c "echo FILE ; awk '{if(max<){max=;datum=}}END{print datum, max}' FILE" >> out.txt
(2)
find ./ -name "*.txt" -exec sh -c "echo {} && awk '{if(max<){max=;datum=}}END{print datum, max}' {}" \; >> out.txt
我不介意使用什么命令(xargs 或 exec 或其他),我只关心输出。
如果所有 .txt 文件都在当前目录中,请尝试 (GNU awk):
awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' *.txt
如果您想在当前目录及其所有子目录中搜索 .txt 文件,请尝试:
find . -name '*.txt' -exec awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' {} +
因为现代 find
有一个 -exec
动作,命令 xargs
很少再需要了。
工作原理
{if(max=="" || max<+0){max=;datum=}}
这将找到第 2 列的最大值并将其和对应的值保存在第 1 列中。
ENDFILE{print FILENAME, datum, max; max=""}
到达每个文件的末尾后,这将从具有最大列 2 的行打印文件名和列 1 和列 2。
此外,在每个文件的末尾,
max
被重置为空字符串。
例子
考虑一个包含这三个文件的目录:
$ cat file1.txt
1 1
2 2
$ cat file2.txt
3 12
5 14
4 13
$ cat file3.txt
1 0
2 1
我们的命令产生:
$ awk '{if(max=="" || max<+0){max=;datum=}}ENDFILE{print FILENAME, datum, max; max=""}' *.txt
file1.txt 2 2
file2.txt 5 14
file3.txt 2 1
BSD awk
如果我们不能使用 ENDFILE,请尝试:
$ awk 'FNR==1 && NR>1{print f, datum, max; max=""} max=="" || max<+0{max=;datum=;f=FILENAME} END{print f, datum, max}' *.txt
file1.txt 2 2
file2.txt 5 14
file3.txt 2 1
因为一个 awk 进程可以分析很多文件,所以这种方法应该很快。
FNR==1 && NR>1{print f, datum, max; max=""}
每次我们开始一个新文件时,我们都会打印前一个文件的最大值。
在awk中,
FNR
是当前文件的行号,NR
是目前读取的总行数。当FNR==1 && NR>1
时,这意味着我们至少完成了一个文件,我们开始下一个文件。max=="" || max<+0{max=;datum=;f=FILENAME}
和之前一样,我们捕获第2列的最大值和第1列的相应数据。我们还将文件名记录为变量
f
。END{print f, datum, max}
读完最后一个文件后,我们打印它的最大行。
find . -name '*.txt' | xargs -n 1 -I FILE awk '(FNR==1) || (max<){max=;datum=} END{print FILENAME, datum, max}' FILE >> out.txt
find . -name '*.txt' -exec awk '(FNR==1) || (max<){max=;datum=} END{print FILENAME, datum, max}' {} \; >> out.txt
(由 OP 编辑打字错误)
如果您有 10,000 个文件,每个文件 100,000 行,如果您像这样为每个文件开始新的 awk
调用,您将等待很长时间,因为您将必须创建 10,000 个进程:
find . -name \*.txt -exec awk ....
我创建了一些测试文件,发现上面的过程在我的 iMac.
上只需要 5 多分钟所以,我决定看看那些可爱的 Intel 内核和我为 Apple 支付的所有可爱的闪存盘或许可以使用 GNU Parallel.
基本上,它会 运行 与您的 CPU 拥有的内核一样多的并行作业 - 在一个体面的 Mac 上可能有 4 或 8 个,并且它可以用它提供给命令的参数:
parallel --tag -q awk 'BEGIN{max=;d=} >max {max=;d=} END{print d,max}' ::: *.txt
这产生了相同的结果,现在 运行s 在 1 分 22 秒内,将近 4 倍的加速,- 不错!但我们可以做得更好……正如上面所说,我们仍然为每个文件调用一个新的 awk
,因此 10,000 个 awks
,但并行地,一次调用 8 个。最好将 OS 允许的尽可能多的文件传递给 运行 的 8 个 awk
中的每一个。幸运的是,GNU Parallel 将通过 -X
选项计算出对我们来说有多少:
parallel -X -q gawk 'BEGINFILE{max=;d=} >max {max=;d=} ENDFILE{print FILENAME,d,max}' ::: *.txt
现在需要 49 秒,但请注意,我对 ENDFILE
/BEGINFILE
使用 gawk
而不是 --tag
选项,因为每个 awk 调用现在都在接收数百个文件,而不仅仅是一个。
GNU Parallel 和 gawk 可以很容易地安装在 Mac 上 homebrew.您只需转到 homebrew website 并将单行代码复制并粘贴到您的终端中。然后你在 macOS 上有一个合适的包管理器,并可以访问数以千计的优质、有用、管理良好的包。
安装 homebrew 后,您可以安装 GNU Parallel:
brew install parallel
并且您可以安装 gawk:
brew install gawk
如果您不需要包管理器,请注意 GNU Parallel 只是一个 Perl 脚本和 macOS 与 Perl 一起发布。因此,您也可以非常简单地安装它:
(wget -O - pi.dk/3 || curl pi.dk/3/ ) | bash
请注意,如果您的文件名超过 25 个字符,您将达到参数长度 262,144 个字符的限制,并收到一条错误消息,告诉您参数列表太长。如果发生这种情况,只需像这样在 stdin
上提供姓名:
find . -name \*.txt -print0 | parallel -0 -X -q gawk 'BEGINFILE{max=;d=} >max {max=;d=} ENDFILE{print FILENAME,d,max}'