使用sed命令在两个字符串之间用下划线替换空格。

3
我有一个包含如下行的文件:
some thing <phrase>a phrase</phrase> some thing else <phrase>other stuff</phrase>
我需要将所有<phrase>标签之间的空格替换为下划线。所以基本上我需要将></之间的每个空格都替换为下划线。我尝试了许多不同的命令,包括sed、awk和perl,但没有一个能运行成功。以下是我尝试过的一些命令。
sed 's@>\s+[</]@_@g' perl -pe 'sub c{$s=shift;$s=~s/ /_/g;$s}s/>.*?[<\/]/c$&/ge'
sed 's@\(\[>^[<\/]]*\)\s+@\1_@g'
awk -v RS='\\[>^[<\]/]*\\]' '{ gsub(/\<(\s+)\>/, "_", RT); printf "%s%s", $0, RT }' infile 我一直在看这两个问题,试图修改答案以使用我需要的字符。
sed substitute whitespace for dash only between specific character patterns https://unix.stackexchange.com/questions/63335/how-to-remove-all-white-spaces-just-between-brackets-using-unix-tools 请问有人可以帮忙吗?

1
命令必须尽可能难以阅读吗? - melpomene
6个回答

5

不要使用正则表达式解析XML/HTML。

use warnings;
use 5.014;  # for /r modifier
use Mojo::DOM;

my $text = <<'ENDTEXT';
some thing <phrase>a phrase</phrase> some thing else <phrase>other stuff</phrase>
ENDTEXT

my $dom = Mojo::DOM->new($text);
$dom->find('phrase')->each(sub { $_->content( $_->content=~tr/ /_/r ) });
print $dom;

输出:

some thing <phrase>a_phrase</phrase> some thing else <phrase>other_stuff</phrase>

更新:Mojolicious 还包含一些语法糖,可以将这段代码压缩成一行:

$ perl -Mojo -pe '($_=x($_))->find("phrase")->each(sub{$_->content($_->content=~tr/ /_/r)})' input.txt

谢谢,我认为由于有很多自由文本与标签混合在一起,所以解析器无法工作。 - gary69
我假设输入文件不是HTML,因为OP将其描述为基于行。 - melpomene
1
它是基于行的,不是XML / HTML文件。 - gary69
Mojo::DOM 在接受内容方面非常宽容,正如示例所示。 - haukex
2
@gary69 "free text" 在 XML 中只是一个文本节点。它可以包含任何内容,除了作为单独节点的 XML 标签。与大多数 HTML/XML 解析器一样,Mojo::DOM 也允许您获取文本节点。 - Grinnz

2

我需要将每个出现在></之间的空格替换为下划线。

这实际上不能达到您想要的效果,因为例如在以下代码中:

some thing <phrase>a phrase</phrase> some thing else <phrase>other stuff</phrase>
                  ^^^^^^^^^^^      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

“between > and </”所包含的子字符串比你想象的要多(如上方标记的^)。

我认为在Perl中表达您的需求最直接的方式是:

perl -pe 's{>[^<>]*</}{ $& =~ tr/ /_/r }eg'

这里使用[^<>]来确保匹配的子字符串不能包含<>(特别是它不能匹配其他<phrase>标签)。

如果这太容易理解了,你也可以这样写:


perl '-pes;>[^<>]*</;$&=~y> >_>r;eg'

1
请查看 https://ideone.com/Oz6ckt。应该是`'s{<phrase>.*?</phrase>}{ $& =~ tr/ /_/r }eg'`。然而,在一般情况下,字符串可能在标签之间有换行符。 - Wiktor Stribiżew
非常感谢。对于我处理的输入,标签之间不会有换行符。 - gary69
@melpomene,我尝试了类似于“perl -lne ' s/(?=>)([^<>]+?)(?=</)/”但它并没有起作用...你能帮我吗? - stack0114106

1
如果你的数据在GNU Sed中是'd':
sed -E ':b s/<(\w+)>([^<]*)\s([^<]*)(<\/\1)/<\1>\2_\3\4/;tb' d

1
另一个 Perl,用于替换 <phrase> 标签之间的内容。
$ export a="some thing <phrase>a phrase</phrase> some thing else <phrase>other stuff</phrase>"

$ echo $a | perl -lne ' s/(?<=<phrase>)(.+?)(?=<\/phrase>)/$x=$1;$x=~s{ }{_}g;sprintf("%s",$x)/ge ;  print '
some thing <phrase>a_phrase</phrase> some thing else <phrase>other_stuff</phrase>

$

编辑

感谢 @haukex,进一步缩短

$ echo $a | perl -lne ' s/(?<=<phrase>)(.+?)(?=<\/phrase>)/$x=$1;$x=~s{ }{_}g;$x/ge ;  print '
some thing <phrase>a_phrase</phrase> some thing else <phrase>other_stuff</phrase>

$

在我看来,s/<phrase>\K(.+?)(?=<\/phrase>)/ 更好一些,这样 <phrase> 就不会成为匹配的一部分。另外,sprintf("%s",$x) 是什么意思? - haukex
@haukex,是的\K没问题,但在这里(?=<phrase>)更易读。使用sprintf(),您可以替换回匹配的字符串(实际上$1=$&)。 - stack0114106
@haukex.. 是的,你说得对。我更新了答案...关于sprintf()函数...我想知道为什么我会错过它...因为在我的项目中有很多%05d替换,但完全忘记了这个简单的变量就可以做到...我真的卡住了!感谢你的帮助。 - stack0114106
@haukex.. 我觉得你可以帮我.. 你知道为什么 /(?=>)(.*?)(?=<\/)/ 能工作但 (?=>)([^<>]+)(?=<\/) 不能吗? - stack0114106
@haukex,谢谢您的解释,现在我明白了。 - stack0114106
显示剩余3条评论

1
这可能适用于您(GNU sed):

sed -E 's/<phrase>|<\/phrase>/\n&/g;ta;:a;s/^([^\n]*(\n[^\n ]*\n[^\n]*)*\n[^\n]*) /\1_/;ta;s/\n//g' file

将标签用换行符隔开。迭代地将两个换行符之间的空格替换为下划线。当没有更多匹配时,删除引入的换行符。

1
使用支持多字符RS和RT的GNU awk:
```html

With GNU awk for multi-char RS and RT:

```
$ awk -v RS='</?phrase>' '!(NR%2){gsub(/\s+/,"_")} {ORS=RT}1' file
some thing <phrase>a_phrase</phrase> some thing else <phrase>other_stuff</phrase>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接