Ame*_*ina 3 zsh xml text-processing csv
我有大量具有相同结构的 XML 文件:
$ cat file_<ID>.xml
...
...
...
<double>1.2342</double>
<double>2.3456</double>
...
...
...
...
Run Code Online (Sandbox Code Playgroud)
其中<double>
每个 XML 文件中此类条目的数量是固定且已知的(在我的特定情况下为 168)。
我需要构建一个csv
包含所有这些 XML 文件内容的文件,如下所示:
file_0001 1.2342 2.3456 ...
file_0002 1.2342 2.3456 ...
Run Code Online (Sandbox Code Playgroud)
等等。
我怎样才能有效地做到这一点?
我想出的最好的是:
$ cat file_<ID>.xml
...
...
...
<double>1.2342</double>
<double>2.3456</double>
...
...
...
...
Run Code Online (Sandbox Code Playgroud)
当我在一个包含 ~10K XML 文件的文件夹中计时上述脚本时,我得到:
./from_xml_to_csv.sh 100.45s user 94.84s system 239% cpu 1:21.48 total
Run Code Online (Sandbox Code Playgroud)
并不可怕,但我希望能处理 100 倍或 1000 倍以上的文件。我怎样才能使这个处理更有效率?
另外,使用我上面的解决方案,我是否会遇到全局扩展达到限制的情况,例如在处理数百万个文件时?(典型"too many args"
问题)。
对于对这个问题的一个很好的解决方案感兴趣的人,请阅读@mikeserve 的回答。到目前为止,它是最快的,也是扩展最好的。
这应该可以解决问题:
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
Run Code Online (Sandbox Code Playgroud)
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/
),则打印一个空格,后跟第三个字段 ( printf " %s", $3
)。使用<
和>
作为分隔符,这将是数字(第一个字段是第一个字段之前的任何内容<
,第二个字段是double
)。如果需要,您可以在此处格式化数字。例如,使用%8.3f
代替%s
任何数字将打印 3 个小数位,并且总长度(包括点和小数位)至少为 8。$path_to_xml/*.xml
: 文件列表> final_table.csv
:final_table.csv
通过重定向输出将结果放入在“argument list to long”错误的情况下,可以使用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
Run Code Online (Sandbox Code Playgroud)
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`,这需要引用,否则 shell 将尝试扩展模式-exec COMMAND {} +
:COMMAND
使用匹配的文件作为参数运行命令代替{}
. +
表示可以一次传递多个文件,从而减少分叉。如果您使用\;
(;
需要被引用,否则它由 shell 解释) 而不是+
单独为每个文件运行命令。您还可以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
Run Code Online (Sandbox Code Playgroud)
-print0
: 输出以空字符分隔的文件列表|
(pipe): 将标准输出重定向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
Run Code Online (Sandbox Code Playgroud)
where BEGINFILE
,ENDFILE
在更改文件时调用(如果您的 awk 支持它)。