複数ファイルのデータを単一のCSVファイルに効率的に抽出

複数ファイルのデータを単一のCSVファイルに効率的に抽出

同じ構造を持つXMLファイルがたくさんあります。

$ cat file_<ID>.xml
... 
 ... 
   ...
      <double>1.2342</double>
      <double>2.3456</double>
      ...
   ...
 ... 
... 

<double>ここで、各 XML ファイルの対応するエントリの数は固定されていることがわかります (私の特別な場合は 168)。

csv次のように、すべてのXMLファイルの内容を格納するファイルを作成する必要があります。

file_0001 1.2342 2.3456 ... 
file_0002 1.2342 2.3456 ... 

など。

これをどのように効率的に実行できますか?


私が思いついた最高は次のとおりです。

#!/usr/bin/env zsh

for x in $path_to_xmls/*.xml; do 

    # 1) Get the doubles ignoring everything else
    # 2) Remove line breaks within the same file
    # 3) Add a new line at the end to construct the CSV file
    # 4) Join the columns together

    cat $x | grep -F '<double>' | \ 
    sed -r 's/.*>([0-9]+\.*[0-9]*).*?/\1/' | \
    tr '\n' ' ' | sed -e '$a\'  |  >> table_numbers.csv

    echo ${x:t} >> file_IDs.csv
done
    
paste file_IDs table_numbers.csv > final_table.csv

〜10K XMLファイルを含むフォルダに上記のスクリプトを配置すると、次の結果が表示されます。

./from_xml_to_csv.sh  100.45s user 94.84s system 239% cpu 1:21.48 total

悪くはありませんが、100倍または1000倍以上のファイルを処理できることを願っています。どうすればこの処理をより効率的にすることができますか?

また、上記のソリューションを使用すると、数百万のファイルを処理するなど、グローバル拡張が限界に達する状況に直面するでしょうか? (典型的な"too many args"質問)。

修正する

この質問に興味がある人は@mikeserveの答えを読んでください。これまでに最も高速でスケーラビリティに優れた製品です。

ベストアンサー1

これにより、トリックを実行できます。

awk -F '[<>]' '
      NR!=1 && FNR==1{printf "\n"} 
      FNR==1{sub(".*/", "", FILENAME); sub(".xml$", "", FILENAME); printf FILENAME} 
      /double/{printf " %s", $3}
      END{printf "\n"}
    ' $path_to_xml/*.xml > final_table.csv

説明する:

  • awk:このプログラムを使用してawkGNU awk 4.0.1でテストしました。
  • -F '[<>]'<: と>フィールド区切り文字として使用
  • NR!=1 && FNR==1{printf "\n"}:全体の最初の行()ではなく、NR!=1ファイルの最初の行(FNR==1)の場合は改行文字を出力します。
  • FNR==1{sub(".*/", "", FILENAME); sub(".xml$", "", FILENAME); printf FILENAME}:ファイルの最初の行の場合は、/ファイル名()から最後()の前をすべて削除し、後の()を削除して結果を印刷()します。sub(".*/", "", FILENAME)FILENAME.xmlsub(".xml$", "", FILENAME)printf FILENAME
  • /double/{printf " %s", $3}行に「double」(/double/)が含まれている場合は、スペースが印刷され、その後に3番目のフィールド(printf " %s", $3)が表示されます。数字になる区切り文字としてと<を使用します(最初のフィールドは最初のフィールドの前にあるもので、2番目のフィールドはです)。必要に応じて、ここで数値書式を指定できます。たとえば、任意の数字の代わりに使用すると、小数点以下の3桁が出力され、全長(スコアと小数点以下の桁数を含む)は少なくとも8桁になります。><double%8.3f%s
  • END{printf "\n"}: 最後の行の後に追加の改行を印刷します (オプションである可能性があります)。
  • $path_to_xml/*.xml: ファイルリスト
  • > final_table.csvfinal_table.csv:結果を入れてください。

「引数リストが長くなる」エラーが発生した場合は、直接渡すのではなく、findwith引数を使用してファイルリストを生成できます。-exec

find $path_to_xml -maxdepth 1 -type f -name '*.xml' -exec awk -F '[<>]' '
      NR!=1 && FNR==1{printf "\n"} 
      FNR==1{sub(".*/", "", FILENAME); sub(".xml$", "", FILENAME); printf FILENAME} 
      /double/{printf " %s", $3}
      END{printf "\n"}
    ' {} + > final_table.csv

説明する:

  • find $path_to_xmlfindファイルを一覧表示するように指示します。$path_to_xml
  • -maxdepth 1: サブフォルダーを入力しないでください$path_to_xml
  • -type f:一般ファイルのみを一覧表示します。 (これも$path_to_xml自分を除きます。)
  • -name '*.xml': only list files that match the pattern*.xml`、引用する必要があります。それ以外の場合、シェルは拡張モードを試みます。
  • -exec COMMAND {} +COMMAND代わりに一致するファイルをパラメータとして使用します{}+複数のファイルを一度に転送できるため、フォークが少なくなります。各ファイルに対して個別にコマンドを実行する代わりに使用される場合\;;引用符が必要、それ以外の場合はシェルで解釈されます)。+

xargs以下と組み合わせて使用​​することもできますfind

find $path_to_xml -maxdepth 1 -type f -name '*.xml' -print0 |
 xargs -0 awk -F '[<>]' '
      NR!=1 && FNR==1{printf "\n"} 
      FNR==1{sub(".*/", "", FILENAME); sub(".xml$", "", FILENAME); printf FILENAME} 
      /double/{printf " %s", $3}
      END{printf "\n"}
    ' > final_table.csv

説明する

  • -print0:ヌル文字で区切られたファイルのリストを出力します。
  • |(パイプ):標準出力をfind標準入力にリダイレクトします。xargs
  • xargs:標準入力からコマンドをビルドして実行します。つまり、渡された各引数(この場合はファイル名)に対してコマンドを実行します。
  • -0:xargs引数がヌル文字で区切られていると仮定します。

awk -F '[<>]' '      
      BEGINFILE {sub(".*/", "", FILENAME); sub(".xml$", "", FILENAME); printf FILENAME} 
      /double/{printf " %s", $3}
      ENDFILE {printf "\n"}
    ' $path_to_xml/*.xml > final_table.csv

whichBEGINFILEENDFILEファイルが変更されたときに呼び出されます(awkがサポートしている場合)。

おすすめ記事