将文件名添加到xargs和awk命令的输出中

时间:2018-01-08 05:00:28

标签: shell awk xargs

我有一个完整的.txt文件目录,每个文件都有两列和多行(> 10000)。对于这些文件中的每一个,我试图在第二列中找到最大值,并将第1列和第2列中的相应条目打印到输出文件中。为此,我有一个工作awk命令。

find ./ -name "*.txt" | xargs -I FILE awk '{if(max<$2){max=$2;datum=$1}}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<$2){max=$2;datum=$1}}END{print datum, max}' FILE >> out.txt

但是我收到了错误:

  

awk:无法打开文件FILE
  源行号1

我尝试了各种其他方法,这些方法可能只有几个字符是正确的:
(1)

find ./ -name "*.txt" | xargs -I FILE -c "echo FILE ; awk '{if(max<$2){max=$2;datum=$1}}END{print datum, max}' FILE" >> out.txt  

(2)

find ./ -name "*.txt" -exec sh -c "echo {} && awk '{if(max<$2){max=$2;datum=$1}}END{print datum, max}' {}" \; >> out.txt

我不介意使用什么命令(xargs或exec或其他),我只关心输出。

3 个答案:

答案 0 :(得分:2)

如果所有.txt文件都在当前目录中,请尝试(GNU awk):

awk '{if(max=="" || max<$2+0){max=$2;datum=$1}}ENDFILE{print FILENAME, datum, max; max=""}' *.txt

如果要搜索.txt文件的当前目录及其所有子目录,请尝试:

find . -name '*.txt' -exec awk '{if(max=="" || max<$2+0){max=$2;datum=$1}}ENDFILE{print FILENAME, datum, max; max=""}' {} +

由于现代find具有-exec操作,因此很少需要命令xargs

如何运作

  • {if(max=="" || max<$2+0){max=$2;datum=$1}}

    这将找到最大列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<$2+0){max=$2;datum=$1}}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<$2+0{max=$2;datum=$1;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<$2+0{max=$2;datum=$1;f=FILENAME}

    与之前一样,我们捕获第2列的最大值和第1列的相应数据。我们还将文件名记录为变量f

  • END{print f, datum, max}

    我们读完最后一个文件后,打印出最大行。

答案 1 :(得分:2)

如果你有10,000个文件,每个100,000行,如果你为这样的每个文件开始awk的新调用,你将需要等待很长时间,因为你必须创建10,000个进程:< / p>

find . -name \*.txt -exec awk ....

我创建了一些测试文件,发现上面的iMac只需要5分多钟。

所以,我决定看看所有那些可爱的英特尔核心以及我付出的所有可爱的闪存盘 Apple 如此可能能够使用 GNU Parallel

基本上,它会并行运行尽可能多的作业,因为你的CPU有核心 - 在一台像样的Mac上可能只有4或8个,并且可以使用提供给命令的参数来标记输出行:

parallel --tag -q awk 'BEGIN{max=$2;d=$1} $2>max {max=$2;d=$1} END{print d,max}' ::: *.txt 

产生相同的结果,现在以1分22秒的速度运行,接近4倍的加速, - 不错!但是我们可以做得更好......正如上面所说的那样,我们仍在为每个文件调用一个新的awk,所以10,000 awks,但并行,一次8个。awk。将操作系统允许的文件传递给我们并行运行的8个-X中的每个文件会更好。幸运的是, GNU Parallel 将使用parallel -X -q gawk 'BEGINFILE{max=$2;d=$1} $2>max {max=$2;d=$1} ENDFILE{print FILENAME,d,max}' ::: *.txt 选项计算出对我们来说有多少:

gawk

现在需要49秒,但请注意我使用的ENDFILE用于BEGINFILE / --tag而不是brew install parallel 选项,因为每次awk调用现在都会接收到数百个文件而不仅仅是一个。

可以使用自制程序在Mac上轻松安装

GNU Parallel gawk 。您只需转到homebrew website,然后将一行内容复制并粘贴到终端中即可。然后,您在macOS上拥有一个合适的软件包管理器,并可访问数千个高质量,实用且管理良好的软件包。

安装自制程序后,您可以安装 GNU Parallel

brew install gawk

您可以使用

安装 gawk
(wget -O - pi.dk/3 || curl pi.dk/3/ ) | bash

如果你不想要一个软件包管理器,值得注意的是 GNU Parallel 只是一个 Perl 脚本,而macOS附带 Perl 无论如何。因此,您也可以非常简单地安装它:

stdin

请注意,如果您的文件名超过大约25个字符,您将在参数长度上达到262,144个字符的限制,并收到一条错误消息,告诉您参数列表太长。如果发生这种情况,只需在find . -name \*.txt -print0 | parallel -0 -X -q gawk 'BEGINFILE{max=$2;d=$1} $2>max {max=$2;d=$1} ENDFILE{print FILENAME,d,max}' 上提供名称,如下所示:

{{1}}

答案 2 :(得分:1)

find . -name '*.txt' | xargs -n 1 -I FILE awk '(FNR==1) || (max<$2){max=$2;datum=$1} END{print FILENAME, datum, max}' FILE >> out.txt

find . -name '*.txt' -exec awk '(FNR==1) || (max<$2){max=$2;datum=$1} END{print FILENAME, datum, max}' {} \; >> out.txt

(由OP编辑为错字)