使用 awk 和 xmllint 缩进 XML-like 文件

Indent XML-like file with awk and xmllint

我有一个 "XML-like" 文件,其中包含大量配置数据。我说 "XML-like" 因为它真的像 3 个 XML 文件连接在一起,用 "]]>]]>"

分隔

例如

<?xml version="1.0" encoding="UTF-8"?>
<hello><world>"Earth"</world></hello>]]>]]><?xml version="1.0" encoding="UTF-8"?>
<data><lemur><type>"Ring-tailed"</type></lemur></data>]]>]]><?xml version="1.0" encoding="UTF-8"?>
<data><lemur><type>"Mouse"</type></lemur></data>]]>]]>

我正在尝试编写一个脚本,它将调用 xmllint 来缩进文件中的所有 XML 标记。但是,xmllint(以及许多其他 xml 格式化程序)似乎要求文件中只有一个 XML 文档。例如。该文件需要以“<?xml version="1.0" encoding="UTF-8"?>”开头并且只包含一个根树。

所以我尝试编写一个 awk 脚本,将数据解析为单独的块并将其传递给 xmllint,但我收到一个无法通过的错误。我已将脚本和输出放在下面。

$ awk '
BEGIN {
    RS = "]]>]]>"
    xmlFormatCommand = "xmllint --format -"
} 

{
    print [=12=] | xmlFormatCommand 
}
' SmallTest.xml

-:3: parser error : XML declaration allowed only at the start of the document
<?xml version="1.0" encoding="UTF-8"?>
     ^
-:4: parser error : Extra content at the end of the document
<data><lemur><type>"Ring-tailed"</type></lemur></data>
^

如果我在两个单独的操作中执行此操作,一个是 awk 打印到三个临时文件,另一个是 xmllint 对这些文件进行操作,那么它就可以工作。

例如

awk 'BEGIN {RS = "]]>]]>"} {print [=13=] > "Section_" NR ".txt" }' SmallTest.xml

这会产生三个文件 Section_1.txt、Section_2.txt 和 Section_3.txt。 Section_2.txt的内容是:

$ cat Section_2.txt
<?xml version="1.0" encoding="UTF-8"?>
<data><lemur><type>"Ring-tailed"</type></lemur></data>

我可以用 xmllint 格式化那个文件:

$ cat Section_2.txt | xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<data>
  <lemur>
    <type>"Ring-tailed"</type>
  </lemur>
</data>

所以我不明白为什么我不能在 awk 脚本中首先将它通过管道传输到 xmllint。

感谢您提供的任何帮助。

-乔恩

简而言之,您的问题是 awk 一直使用同一个管道。管道以与打开时完全相同的字符串被记住(这意味着你不能 运行 同时执行两次完全相同的命令),并且记录一个接一个地写入其中,所以你有只有一个 xmllint 进程将整个文件作为输入。

您可以通过在每条记录后关闭管道来解决此问题:

$ awk '
BEGIN {
    RS = "]]>]]>"
    xmlFormatCommand = "xmllint --format -"
} 

{
    print [=10=] | xmlFormatCommand 
    close(xmlFormatCommand)      # <-- HERE
}
' SmallTest.xml

这里 close 接受管道被记住的标识符作为参数(命令)。我知道与其他编程语言相比这看起来很奇怪。

由于您在问题中的文件末尾会有一条空记录,顺便说一下,您可能想在其中放置一个条件来排除此类空记录。例如,

$ awk '
BEGIN {
    RS = "]]>]]>"
    xmlFormatCommand = "xmllint --format -"
} 

! /^\s*$/ {  # <-- HERE
    print [=11=] | xmlFormatCommand 
    close(xmlFormatCommand)
}
' SmallTest.xml

其中 /^\s*$/ 匹配开始和结束之间只有空格的记录,! 反转匹配。

这是因为打印命令的输出一直转到 xmllint 的同一个实例。

解决此问题的最简单方法是仅使用 xmllint 创建输出文件:

awk '
    BEGIN {
    RS = "]]>]]>"
} 
{
    print [=10=] | "xmllint --format --output sample_"NR".xml -"
}
' SmallTest.xml

如果你这样做,你会留下一个错误,因为 xmllint 将在最后一行之后被调用一次而没有留下任何输入 - 所以你可以只删除源代码中的最后一个分隔符 xml 或检查 $0 在 awk 脚本中是否有值。

要将所有内容输出到标准输出,请执行:

awk '
BEGIN {
RS = "]]>]]>"
} 
{
print [=11=] | "xmllint --format -"
close("xmllint --format -")}
' SmallTest.xml

https://www.gnu.org/software/gawk/manual/html_node/Close-Files-And-Pipes.html