我有一个非常庞大的文件,如下所示:
<a>text</a>text
blah
<b>data1</b>abc<b>data2</b>
<b>data3</b>blahblah
<c>text</c>
<d>text</d>
<x>blahblah<b>data4
data5
data6</b>
<b>data7
</x>
也就是说,它的格式是不可预测的。我需要提取每个<b>...</b>
项(它可能包含多行文本!)并将它们中的每一个放在一个单独的行中。同时,我需要用一个空格替换换行符和空格。
期望的输出:
<b>data1</b>
<b>data2</b>
<b>data3</b>
<b>data4 data5 data6</b>
我发现的只有两步之遥:
gawk '{if ($0 != "") { printf "%s", gensub(/\s+/, " ", "g", gensub(/\s+$/, "", "g", $0)) } }' path/to/input.txt > path/to/single-line.txt
然后
grep -Pzo '(?s)<b>.*?</b>' path/to/single-line.txt > path/to/output.txt
但我不喜欢它!必须将多GB文本文件转换为单行...似乎不太好。是否有可能在一次通过中“即时”解决这样的问题?
答案 0 :(得分:1)
假设您的文档格式正确,即<b>
开始标记始终与</b>
结束标记匹配,那么这可能就是您所需要的:
sed 's@<[/]\?b>@\n&\n@g' path/to/input.txt |
awk 'BEGIN {buf=""}
/<b>/ {Y=1; buf=""}
/<\/b>/ {Y=0; print buf"</b>"}
Y {buf = buf$0}
' | tr -s ' '
输出:
<b>data1</b>
<b>data2</b>
<b>data3</b>
<b>data4 data5 data6</b>
说明:
我们首先使用sed 's@<[/]\?b>@\n&\n@g'
将<b>
和</b>
移到自己的行中。
然后我们用awk实现一个简单的解析器:
BEGIN {buf=""}
:初始化缓冲区/<b>/ {Y=1; buf=""}
:找到<b>
时,启用捕获(Y = 1)并清空缓冲区/<\/b>/ {Y=0; print buf"</b>"}
:找到</b>
时,禁用捕获并打印缓冲内容以及结束标记Y {buf = buf$0}
:如果捕获标志为true,则将当前行追加到缓冲区最后,我们通过tr -s ' '
传递输出以将多个空格挤压到单个空格中。
如果你想要一行:
sed 's@<[/]\?b>@\n&\n@g' in.txt | awk 'BEGIN{B=""} /<b>/{Y=1;B=""} /<\/b>/{Y=0;print B"</b>"} Y{B=B$0}' | tr -s ' '
或将其另存为shell脚本(extract_b.sh
):
#!/usr/bin/sh
sed 's@<[/]\?b>@\n&\n@g' "$1" | awk 'BEGIN{B=""} /<b>/{Y=1;B=""} /<\/b>/{Y=0;print B"</b>"} Y{B=B$0}' | tr -s ' '
并像这样使用它:
extract_b.sh path/to/input.txt > /path/to/output.txt
还使用mawk
进行了测试,速度更快(在我的测试中为27 Mb / s与17Mb / s),您可能更喜欢将它用于多GB文件。
答案 1 :(得分:0)
我将假设你的源文件是XML - 看起来像它。 如果是,那么基本上,基于正则表达式的解析不能很好地工作 - 语义相同的XML将匹配不同的模式。
所以我建议这里的工作工具是一个XML解析器。我会这样做:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
XML::Twig->new(
'twig_handlers' => {
'b' => sub { print $_ ->text_only =~ s/\s+/ /grs, "\n"; }
}
)->parse( <> );
这将贯穿您的源数据,并随时打印b
元素。
但是内存占用问题有点大。 XML需要大约10倍的内存,这真是坏消息。幸运的是,对于XML::Twig
,您可以使用purge
方法处理该情况:
'_default_' => sub { $_[0] -> purge; }
这会在每个元素上设置一个处理程序(不是b
),并且将清除内存数据到目前为止。
E.g。
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
XML::Twig->new(
'twig_handlers' => {
'b' => sub { print $_ ->text_only =~ s/\s+/ /grs, "\n"; }
'_default_' => sub { $_[0] -> purge; }
}
)->parse( <> );
使用./myscript.pl <yourfile>
运行它。
如果您愿意,可以将其展开以便清晰:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
sub handle_b {
my ( $twig, $b_element ) = @_;
my $b_text = $b_element -> text_only;
$b_text =~ s/\s+/ /gs; #replace multiline space with single space.
print $b_text,"\n";
}
sub purge_as_we_go {
my ( $twig, $element ) = @_;
$twig -> purge;
}
XML::Twig->new(
'twig_handlers' => {
'b' => \&handle_b,
'_default_' => \&purge_as_we_go,
}
)->parse( <> );
我们使用在perl中具有特殊含义的钻石运算符<>
- 它是{em> 数据在STDIN
上传输(因此我们可以cat XMLFILE | ./myscript.pl
)或者它打开在命令行上提供的文件并读取它们(./myscript.pl some_xml_file
。
这类似于大多数unix工具的工作方式,但显然如果您愿意,可以parse ( <STDIN> );
或parsefile ( $ARGV[0] );
编辑:只是发现你也在标记之后 - 在这种情况下,您可以使用$element -> sprint
来提供元素/属性等而不是text_only
。
请注意 - 这可能不适用于嵌套元素。