XARGSでAWKを使用すると発生する問題

XARGSでAWKを使用すると発生する問題

次のコマンドの実行に問題があります -->

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スクリプトは複数のバッチの入力ファイルで呼び出され、BEGINawkが呼び出されるたびにその部分が実行されます。実行を開始する前に、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の後と,残りの出力のsの前の偽の空白を削除しました。

答え2

xargs(cross-argsの場合)は、入力から単語を読み取り、それをコマンドに渡すcrossコマンドargです。

その入力は無限に長くすることができますが、コマンドに渡すことができる引数の数は制限されていますが、そうでなくても引数リストを完全に渡す必要があるため、連続した引数を渡したくありません。入力にワードストリームがある場合は、xargsすべての読み取りを渡し、すべてメモリに保存し、入力の終わりに達した場合にのみコマンドを開始する必要があります(存在する場合)。

また、find単語リスト(ここではファイルパス)は、デフォルトで予想される形式では生成されませんxargs。それらを互いに接続するには標準がfind ... -print0 | xargs -r0 cmd...必要ですfind ... -exec cmd... {} +

したがって、ファイルのリストが十分に大きい場合、通常はxargs(あなたの場合)複数回実行され、cmdそのステートメントは毎回実行されます。awkawkBEGIN

多くのGNUコマンド(wc、、、sort...)には最近のオプション(またはGNUまたはGNUの述語)がdu追加され、ファイルのNUL区切り文字または標準入力(このような)を引数として扱うためにファイルのリストを取得します。制限を避け、リスト全体をメモリに保存せず、標準入力からファイルを読み取るとすぐにファイル処理を開始できることを意味します。--files0-from-files0-fromfind--null --verbatim-files-from --files-fromtarxargs -r0

例えば、

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

ファイルが見つかったら、wファイルの注文数を印刷し、最後に1行を印刷します。これは同時に実行されないwhereよりはるかに優れており、whereは複数行を出力できます。.txtfindtotalfind . -name '*.txt' -type f -exec wc -w --total=always {} +findwctotal

GNUにはawkまだそのようなオプションはありませんが、次のように直接実装できます。

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(この特別なケースでは、そのヘッダを次の外部に印刷する方がはるかに簡単です。エドが見せた)。

次のものと同じ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'

関連情報