同じ構造を持つ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
:このプログラムを使用してawk
GNU 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
.xml
sub(".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.csv
final_table.csv
:結果を入れてください。
「引数リストが長くなる」エラーが発生した場合は、直接渡すのではなく、find
with引数を使用してファイルリストを生成できます。-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_xml
:find
ファイルを一覧表示するように指示します。$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
whichBEGINFILE
はENDFILE
ファイルが変更されたときに呼び出されます(awkがサポートしている場合)。