我正在使用xmllint --shell
一个大型 XML 文件,并使用该write
命令写出要在测试中使用的 XML 片段。写出的代码片段需要原始 XML 文件中的几行(声明、命名空间和根节点)。我希望能够将这些行添加到文件中,而无需手动复制这些行。相反,我想使用 sed 为我附加这些行,这样我就可以编写一个函数来自动执行这项非常繁琐的任务。为了说明这一点,这是我想要完成的任务的一个示例。
源 XML (source.xml):
<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
<foo:Entry>
<foo:SomeNode>Foo1</foo:SomeNode>
<foo:AnotherNode>Bar1</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo2</foo:SomeNode>
<foo:AnotherNode>Bar2</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo3</foo:SomeNode>
<foo:AnotherNode>Bar3</foo:AnotherNode>
</foo:Entry>
<!-- tens of thousands of others -->
<foo:Entry>
<foo:SomeNode>Foo20432</foo:SomeNode>
<foo:AnotherNode>Bar20432</foo:AnotherNode>
</foo:Entry>
</foo:root>
保存的 XML 片段 (sample.xml):
<foo:Entry>
<foo:SomeNode>Foo</foo:SomeNode>
<foo:AnotherNode>Bar</foo:AnotherNode>
</foo:Entry>
所以我需要用 source.xml 的顶部两行和底部一行来包裹它。但由于角色的原因,以下失败<
:
$ sed -i 1i"`head -n 2 source.xml`" sample.xml
sed: -e expression #1, char 43: unknown command: `<'
当从这样的子命令提供该字符时,有没有办法转义该字符?
答案1
命令sed
“i”需要后跟文本,如给定上面提供的命令时\
BSD 的输出中所解释的那样。sed
然而,它只期望一文本行。要插入更多内容,您需要在第一行末尾添加反斜杠:
sed "1i\\
$(head -n 2 source.xml | sed 's/$/\\/')
" sample.xml
这(嵌套sed
调用)有点荒谬。正如我所写的别处,就地脚本文件编辑选择的工具不是sed
,而是ex
:
ex -sc '1,2ya | n! | 0pu | x' source.xml sample.xml
该-s
标志ex
以静默模式启动,用于批处理。 -c
指定要运行的命令。
1,2ya
猛拉(即复制)第一个文件的前两行,source.xml
.
|
是命令分隔符。
n!
转到下一个文件,放弃对当前文件所做的任何更改。 (我们在这种情况下没有做任何事情,所以n
也可以。)
0pu
“放置”(即粘贴)我们之前复制的行,将它们放置在行“0”之后(即,将它们粘贴到第一行上方)。
x
退出,保存对当前文件所做的更改。
与 POSIX 中未指定的命令不同sed -i
(并且在 BSD 上不起作用,BSDsed
需要给定备份文件扩展名,-i
即使为空),上面的ex
命令完全是符合 POSIX 标准。
答案2
当您插入/追加多行时,您必须转义行尾,以便sed
知道何时停止插入/追加。在你的情况下你可以运行
head -n 2 source.xml | sed '1i\
1i\\
s/\\/&&/g
$!s/$/\\/' | sed -f - sample.xml
第一个sed
处理输入(1i\
在这两行之前添加命令,转义任何反斜杠以及行尾(如果不是最后一行)并将其作为脚本传递sed
给第二个命令。如果您想就地编辑,请添加-i
到第二个。sed
答案3
不要sed
与 一起使用XML
。 XML 是一种上下文数据结构,而正则表达式根本无法很好地支持。https://stackoverflow.com/questions/1732348/regex-match-open-tags- except-xhtml-self-contained-tags
使用解析器。perl
效果XML::Twig
很好:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use XML::Twig;
my $xml_to_insert = XML::Twig -> parse ( '<foo:Entry>
<foo:SomeNode>Foo</foo:SomeNode>
<foo:AnotherNode>Bar</foo:AnotherNode>
</foo:Entry>') -> root -> copy;
my $xml = XML::Twig -> parse ( \*DATA );
$xml_to_insert -> paste ( 'first_child', $xml -> root );
$xml -> set_pretty_print ( 'indented_a');
$xml -> print;
__DATA__
<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
<foo:Entry>
<foo:SomeNode>Foo1</foo:SomeNode>
<foo:AnotherNode>Bar1</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo2</foo:SomeNode>
<foo:AnotherNode>Bar2</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo3</foo:SomeNode>
<foo:AnotherNode>Bar3</foo:AnotherNode>
</foo:Entry>
<!-- tens of thousands of others -->
<foo:Entry>
<foo:SomeNode>Foo20432</foo:SomeNode>
<foo:AnotherNode>Bar20432</foo:AnotherNode>
</foo:Entry>
</foo:root>
输出:
<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
<foo:Entry>
<foo:SomeNode>Foo</foo:SomeNode>
<foo:AnotherNode>Bar</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo1</foo:SomeNode>
<foo:AnotherNode>Bar1</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo2</foo:SomeNode>
<foo:AnotherNode>Bar2</foo:AnotherNode>
</foo:Entry>
<foo:Entry>
<foo:SomeNode>Foo3</foo:SomeNode>
<foo:AnotherNode>Bar3</foo:AnotherNode>
</foo:Entry>
<!-- tens of thousands of others -->
<foo:Entry>
<foo:SomeNode>Foo20432</foo:SomeNode>
<foo:AnotherNode>Bar20432</foo:AnotherNode>
</foo:Entry>
</foo:root>
为了便于说明,这更长、更详细 - 但它本质上是获取您的代码片段,并将其复制粘贴到您的结构中。又好又简单。
XML::Twig
还支持“parsefile_inplace”,它允许您执行与sed -i
.所以你的例子看起来更像是:
my $xml_to_insert = XML::Twig -> parsefile ( 'source.xml' ) -> root -> copy;
XML::Twig -> new ( pretty_print => 'indented_a',
twig_handlers => {
'foo:root' => sub {
$xml_to_insert -> paste ( 'first_child', $_ )
} }) -> parsefile_inplace ('sample.xml');
或者如果这看起来有点太复杂了:
sub insert_source {
my ( $twig, $branch ) = @_;
my $xml_to_insert = XML::Twig -> parsefile ( 'source.xml' ) -> root -> copy;
$xml_to_insert -> paste ( 'first_child', $branch );
}
my $xml = XML::Twig -> new ( twig_handlers => { 'foo:root' => \&insert_source } );
$xml -> parsefile_inplace ( 'sample.xml');