【问题标题】:Add filename to output of an xargs and awk command将文件名添加到 xargs 和 awk 命令的输出
【发布时间】:2018-06-17 02:05:13
【问题描述】:

我有一个充满 .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 或其他),我只关心输出。

【问题讨论】:

  • 你说你有一个 "full" 文件目录 - 请问大概有多少?
  • 大约10,000个文件,每个文件的行数大约为100,000

标签: shell awk xargs


【解决方案1】:

如果所有 .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&lt;$2+0){max=$2;datum=$1}}

    这会找到第 2 列的最大值并将其和对应的值保存在第 1 列中。

  • ENDFILE{print FILENAME, datum, max; max=""}

    到达每个文件的末尾后,这将打印文件名和第 1 列和第 2 列,从最大列 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 &amp;&amp; NR&gt;1{print f, datum, max; max=""}

    每次我们开始一个新文件时,我们都会打印上一个文件的最大值。

    在 awk 中,FNR 是当前文件的行号,NR 是到目前为止读取的总行数。当FNR==1 &amp;&amp; NR&gt;1 时,这意味着我们已经完成了至少一个文件,我们将开始下一个文件。

  • max=="" || max&lt;$2+0{max=$2;datum=$1;f=FILENAME}

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

  • END{print f, datum, max}

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

【讨论】:

  • 你应该提到 ENDFILE 需要 GNU awk。
  • 感谢您的回答和解释!我特别喜欢 or 语句。
  • 看来我必须用 END 替换 ENDFILE 才能执行任何操作,然后它只会在停止之前对单个文件起作用。正如 Ed Morton 所说,这个命令需要 GNU awk。我应该说我在使用 zsh 的 OS X 上,并且大概没有 GNU awk。是否有适当的修改?
  • @user9186266 很抱歉!我在答案的末尾添加了一个 BSD 友好的版本。
【解决方案2】:
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 编辑​​错字)

【讨论】:

    【解决方案3】:

    如果您有 10,000 个文件,每个文件 100,000 行,那么如果您为每个这样的文件开始新的 awk 调用,您将等待很长时间,因为您将不得不创建 10,000 个进程:

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

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

    所以,我决定看看所有那些可爱的 Intel 内核和所有我为 Apple 付出高昂代价的可爱闪存盘可能能够使用 GNU 并行

    基本上,它会并行运行与您的 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 个。最好将操作系统允许的尽可能多的文件传递给并行运行的 8 个awks 中的每一个。幸运的是,GNU Parallel 会通过 -X 选项计算出对我们来说有多少:

    parallel -X -q gawk 'BEGINFILE{max=$2;d=$1} $2>max {max=$2;d=$1} ENDFILE{print FILENAME,d,max}' ::: *.txt 
    

    现在需要 49 秒,但请注意,我将 gawk 用于 ENDFILE/BEGINFILE 而不是 --tag 选项,因为每个 awk 调用现在接收数百个文件,而不仅仅是一个。


    GNU Parallelgawk 可以通过 homebrew 轻松安装在 Mac 上。您只需转到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=$2;d=$1} $2>max {max=$2;d=$1} ENDFILE{print FILENAME,d,max}'
    

    【讨论】:

    • 这确实提供了显着的加速。我拥有的文件名通常为 40 个字符,但正如您所说,通过标准输入输入时没有问题。我没有检查标准输入提要是否会影响速度,但它的速度已经足够不关心了。
    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-29
    • 2013-01-17
    • 2016-12-14
    相关资源
    最近更新 更多