将 AWK 与 XARGS 结合使用时出现的问题

将 AWK 与 XARGS 结合使用时出现的问题

我在执行以下命令时遇到问题 -->

find . -type f -name 'out*' |
  xargs awk 'BEGIN{print "Filename, Energy"}/TOTAL ENERGY/{print FILENAME, "," $4}' >> energy.csv

我希望它查看输出文件的所有目录,解析出能量,然后将其与标题列一起写入文件 energy.csv。

问题是,有时它会在文件中间多次写入标题列,但并非总是如此。我不理解这种行为。

答案1

xargs(或find)将调用您告诉他们的任何命令,一次传递该命令任意数量的文件名,总是少于会导致ARG_MAX超出的文件名。

因此,您的 awk 脚本将被多批输入文件调用,并且BEGIN每次调用 awk 时都会执行其部分。我们可以通过在开始运行之前在 awk 脚本外部执行标题行的初始打印来避免该问题find

所以,这样做:

{
    ofs=','
    printf '%s%s%s\n' 'Filename' "$ofs" 'Energy' &&
    find . -type f -name 'out*' |
        xargs awk -v OFS="$ofs" '/TOTAL ENERGY/{print FILENAME, $4}'
} > energy.csv

或者这个(find可以调用命令本身,您不需要将其输出通过管道传输到xargs不太健壮的地方):

{
    ofs=','
    printf '%s%s%s\n' 'Filename' "$ofs" 'Energy' &&
    find . -type f -name 'out*' -exec \
        awk -v OFS="$ofs" '/TOTAL ENERGY/{print FILENAME, $4}' {} +
} > energy.csv

我也使 awk 部分变得更加惯用,并消除了,标题中的 后面和,输出其余部分中的 s 之前的虚假空白。

答案2

xargs(对于 cross-args)是一个命令,它读取输入中的单词并将它们cross作为arg命令传递给它们。

它的输入可以是无限长的,但是可以传递给命令的参数数量是有限的,即使不是,因为参数列表必须作为一个整体传递,所以也不希望传递对于输入上的连续单词流,xargs需要将它们全部读取,将它们全部存储在内存中,并且只有当到达输入结束时(如果有的话)才会启动命令。

另请注意,find不会以 . 默认情况下预期的格式生成单词列表(此处为文件路径)xargs。要将它们链接在一起,您需要find ... -print0 | xargs -r0 cmd...或仅需要标准find ... -exec cmd... {} +.

因此,如果文件列表足够大,xargs通常会运行cmd(在您的情况下)几次,并且每次都会运行其语句。awkawkBEGIN

许多 GNU 命令(wcsortdu...)最近添加了一个--files0-from选项(或GNU或GNU 中-files0-from的谓词),以便它们获取文件列表来处理文件或标准输入中的 NUL 分隔(就像这样做)作为参数,它避免了限制,避免将整个列表存储在内存中,并且意味着它们可以在从标准输入读取文件后立即开始处理文件。find--null --verbatim-files-from --files-fromtarxargs -r0

例如,

find . -name '*.txt' -type f -print0 | wc --files0-from - -w --total=always

一旦找到文件,就会打印文件w中的订单数,并在最后打印一行,这比where好得多,并且不会同时运行,而且 where可以输出多行。.txtfindtotalfind . -name '*.txt' -type f -exec wc -w --total=always {} +findwctotal

GNUawk还没有这样的选项,但是您应该能够使用以下方法自己实现它:

find . -type f -name 'out*' -print0 | sort -Vz |
  gawk '
    function inputfile(  old_RS,ret) {
      if (ARGC > 1) delete ARGV[ARGC - 1]
      old_RS = RS
      RS = "\0"
      ret = getline ARGV[ARGC++] < "-"
      RS = old_RS
      if (ret <= 0) exit(-ret)
    }
    BEGIN  {inputfile()}
    ENDFILE{inputfile()}

    # then your awk script
    BEGIN{
      OFS = ","
      print "Filename", "Energy"
    }
    /TOTAL ENERGY/ {print FILENAME, $4}' >> energy.csv

awk(尽管在这种特殊情况下,在as之外打印该标头要简单得多埃德已经表明)。

与 的等效perl -lan项类似于:

find . -type f -name 'out*' -print0 | sort -Vz |
  perl -lane '
    sub nextfile {
      local $/ = "\0";
      my $file = <STDIN> or exit;
      shift @ARGV;
      push @ARGV, $file
    }
    BEGIN {nextfile}

    BEGIN {$, = ","; print "Filename", "Energy"}
    print $ARGV, $F[3] if /TOTAL ENERGY/;

    nextfile if eof'

相关内容