我一直在阅读有关在堆栈中删除重复行的信息。有perl,awk和sed解决方案,但没有一个具体到我想要的,我不知所措。
我想使用快速bash / shell perl命令从此XML案例INSENSITIVELY中删除重复的<path>
标记。保留所有其他重复行(例如<start>
和<end>
)!
输入XML:
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start> <------ Duplicate line to keep
<end>2017-04-20</end> <------ Duplicate line to keep
</model>
<model type="B">
<start>2016-04-20</start> <------ Duplicate line to keep
<end>2017-04-20</end> <------ Duplicate line to keep
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
<upath>/example/dir/here</upath> <------ Duplicate line to REMOVE
</userinterface>
</package>
到目前为止,我已经能够抓住重复的行,但不知道如何删除它们。以下
grep -H path *.[Xx][Mm][Ll] | sort | uniq -id
给出结果:
test.xml: <upath>/example/dir/here</upath>
如何立即删除该行?
在下面执行perl版本或awk版本也会删除<start>
和<end>
日期。
perl -i.bak -ne 'print unless $seen{lc($_)}++' test.xml
awk '!a[tolower($0)]++' test.xml > test.xml.new
答案 0 :(得分:2)
以下脚本接受XML文件作为第一个参数,使用xmlstarlet
(脚本中的xml
)来解析 XML 树和Associative Array (需要Bash 4)来存储唯一的<upath>
节点值。
#!/bin/bash
input_file=$1
# XPath to retrieve <upath> node value.
xpath_upath_value='//package/userinterface/upath/text()'
# XPath to print XML tree excluding <userinterface> part.
xpath_exclude_userinterface_tree='//package/*[not(self::userinterface)]'
# Associative array to help us remove duplicated <upath> node values.
declare -A arr
print_userinterface_no_dup() {
printf '%s\n' "<userinterface>"
printf '<upath>%s</upath>\n' "${arr[@]}"
printf '%s\n' "</userinterface>"
}
# Iterate over each <upath> node value, lower-case it and use it as a key in the associative array.
while read -r upath; do
key="${upath,,}"
# We can remove this 'if' statement and simply arr[$key]="$upath"
# if it doesn't matter whether we remove <upath>foo</upath> or <upath>FOO</upath>
if [[ ! "${arr[$key]}" ]]; then
arr[$key]="$upath"
fi
done < <(xml sel -t -m "$xpath_upath_value" -c \. -n "$input_file")
printf '%s\n' "<package>"
# Print XML tree excluding <userinterface> part.
xml sel -t -m "$xpath_exclude_userinterface_tree" -c \. "$input_file"
# Print <userinterface> tree without duplicates.
print_userinterface_no_dup
printf '%s\n' "</package>"
测试(脚本名称为 sof ):
$ ./sof xml_file
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
<model type="B">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here2</upath>
<upath>/Example/Dir/Here</upath>
</userinterface>
</package>
如果我的评论没有让您的代码足够清晰,请询问,我会相应地回答并编辑此解决方案。
我的xmlstarlet
版本为 1.6.1 ,针对 libxml2 2.9.2 和 libxslt 1.1.28 编译。
答案 1 :(得分:2)
如果您正在解析XML,那么您真的应该使用解析器。有多种选择 - 但是不要使用正则表达式,因为它们是一条真正脆弱代码的途径 - 出于你找到的所有原因。
但它的长短是 - XML是一种语境语言。正则表达式不是。 XML中也存在一些完全有效的差异,这些差异在语义上是相同的,正则表达式无法处理。
E.g。一元标签,可变缩进,不同位置和换行的标签路径。
我可以用不同的方式格式化你的源XML - 所有这些都是有效的XML,说同样的事情。但哪会破坏基于正则表达式的解析。这是有待避免的事情 - 有一天,神秘的是,你的脚本会因为在XML规范中有效的上游更改而没有特殊原因而中断。
这就是你应该使用解析器的原因:
我喜欢XML::Twig
这是一个perl
模块。你可以做你想要的事情:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
my %seen;
#a subroutine to process any "upath" tags.
sub process_upath {
my ( $twig, $upath ) = @_;
my $text = lc $upath -> trimmed_text;
$upath -> delete if $seen{$text}++;
}
#instantiate the parser, and configure what to 'handle'.
my $twig = XML::Twig -> new ( twig_handlers => { 'upath' => \&process_upath } );
#parse from our data block - but you'd probably use a file handle here.
$twig -> parse ( \*DATA );
#set output formatting
$twig -> set_pretty_print ( 'indented_a' );
#print to STDOUT.
$twig -> print;
__DATA__
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
<model type="B">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
<upath>/example/dir/here</upath>
</userinterface>
</package>
这是用于说明概念的长形式,它输出:
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
<model type="B">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
</userinterface>
</package>
通过parsefile_inplace
方法可以大大减少它。
答案 2 :(得分:1)
如果你想只相互忽略重复的行,你可以存储前一行并与之比较。为了忽略这种情况,您可以在双方的比较中使用tolower()
:
awk '{ if (tolower(prev) != $0) print; prev = $0 }'
答案 3 :(得分:0)
嘿,我以前从未用Perl做过,但是有Introductory Tutorial和所有......这些都不是那么简单明了。阅读XML::SAX::ParserFactory和XML::SAX::Base我想出了您在此答案底部看到的代码。
好的,我发现你已经有两个<start>
个标签与日期匹配,两个<end>
标签的日期与整个文件中的相匹配,但这些是在不同的部分。如果您的所有重复行实际上也与相邻,因为在您的示例中 ,您只需要使用uniq
命令来自GNU Coreutils或同等学历。这个命令可以通过正确使用LC_COLLATE
环境变量设置来忽略大小写,但老实说,我发现很难发现一个例子或read how to use LC_COLLATE
忽略大小写。
继续使用解析器:
#!/usr/bin/perl
use XML::SAX;
my $parser = XML::SAX::ParserFactory->parser(
Handler => TestXMLDeduplication->new()
);
my $ret_ref = $parser->parse_file(\*TestXMLDeduplication::DATA);
close(TestXMLDeduplication::DATA);
print "\n\nDuplicates skipped: ", $ret_ref->{skipped}, "\n";
print "Duplicates cut: ", $ret_ref->{cut}, "\n";
package TestXMLDeduplication;
use base qw(XML::SAX::Base);
my $inUserinterface;
my $inUpath;
my $upathSeen;
my $defaultOut;
my $currentOut;
my $buffer;
my %seen;
my %ret;
sub new {
# Idealy STDOUT would be an argument
my $type = shift;
#open $defaultOut, '>&', STDOUT or die "Opening STDOUT failed: $!";
$defaultOut = *STDOUT;
$currentOut = $defaultOut;
return bless {}, $type;
}
sub start_document {
%ret = ();
$inUserinterface = 0;
$inUpath = 0;
$upathSeen = 0;
}
sub end_document {
return \%ret;
}
sub start_element {
my ($self, $element) = @_;
if ('userinterface' eq $element->{Name}) {
$inUserinterface++;
%seen = ();
}
if ('upath' eq $element->{Name}) {
$buffer = q{};
undef $currentOut;
open($currentOut, '>>', \$buffer) or die "Opening buffer failed: $!";
$inUpath++;
}
print $currentOut '<', $element->{Name};
print $currentOut attributes($element->{Attributes});
print $currentOut '>';
}
sub end_element {
my ($self, $element) = @_;
print $currentOut '</', $element->{Name};
print $currentOut '>';
if ('userinterface' eq $element->{Name}) {
$inUserinterface--;
}
if ('upath' eq $element->{Name}) {
close($currentOut);
$currentOut = $defaultOut;
# Check if what's in upath was seen (lower-cased)
if ($inUserinterface && $inUpath) {
if (!exists $seen{lc($buffer)}) {
print $currentOut $buffer;
} else {
$ret{skipped}++;
$ret{cut} .= $buffer;
}
$seen{lc($buffer)} = 1;
}
$inUpath--;
}
}
sub characters {
# Note that this also capture indentation and newlines between tags etc.
my ($self, $characters) = @_;
print $currentOut $characters->{Data};
}
sub attributes {
my ($attributesRef) = @_;
my %attributes = %$attributesRef;
foreach my $a (values %attributes) {
my $v = $a->{Value};
# See also XML::Quote
$v =~ s/&/&/g;
$v =~ s/</</g;
$v =~ s/>/>/g;
$v =~ s/"/"/g;
print $currentOut ' ', $a->{Name}, '="', $v, '"';
}
}
__DATA__
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
<model type="B">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
<upath>/example/dir/here</upath>
</userinterface>
<userinterface>
<upath>/Example/Dir/<b>Here</b></upath> <upath>/Example/Dir/Here2</upath>
<upath>/example/dir/<b>here</b></upath>
</userinterface>
</package>
这不再适用于行,而是在upath
标记内找到userinterface
标记,如果它们在该父组中重复,则会删除它们。保留周围的缩进和换行符。如果upath
标记内有upath
个标记,也会有点奇怪。
看起来像这样:
$ perl saxEG.pl
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
<model type="B">
<start>2016-04-20</start>
<end>2017-04-20</end>
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
</userinterface>
<userinterface>
<upath>/Example/Dir/<b>Here</b></upath> <upath>/Example/Dir/Here2</upath>
</userinterface>
</package>
Duplicates skipped: 2
Duplicates cut: <upath>/example/dir/here</upath><upath>/example/dir/<b>here</b></upath>
答案 4 :(得分:0)
$ awk '!(/<upath>/ && seen[tolower($1)]++)' file
<package>
<id>1523456789</id>
<models>
<model type="A">
<start>2016-04-20</start> <------ Duplicate line to keep
<end>2017-04-20</end> <------ Duplicate line to keep
</model>
<model type="B">
<start>2016-04-20</start> <------ Duplicate line to keep
<end>2017-04-20</end> <------ Duplicate line to keep
</model>
</models>
<userinterface>
<upath>/Example/Dir/Here</upath>
<upath>/Example/Dir/Here2</upath>
</userinterface>
</package>