在 perl 中使用 lookaheads 将空格替换为下划线

substituting spaces for underscores using lookaheads in perl

我有很多行如下形式的文件:

word -0.15636028 -0.2953045 0.29853472 .... 

(数百个浮点数前面的一个单词,由空格分隔)

由于一些我无法控制的错误,这个词有时会有空格。

a bbb c -0.15636028 -0.2953045 0.29853472  .... (several hundreds floats)

我希望用下划线代替,以便得到:

a_bbb_c -0.15636028 -0.2953045 0.29853472  .... (several hundreds floats)

已经为每一行尝试了以下替换代码:

s/\s(?=(\s-?\d\.\d+)+)/_/g;

因此环顾四周显然不是解决方案。 如果能提供任何线索,我将不胜感激。

这样的东西对你有用吗:

s/\s+/_/g;
s/_(-?\d+\.)/ /g;

根据您的评论假设您的文件如下所示:

name float1 float2 float3
a bbb c -0.15636028 -0.2953045 0.29853472
abbb c -0.15636028 -0.2953045 0.29853472
a bbbc -0.15636028 -0.2953045 0.29853472
ab    bbc -0.15636028 -0.2953045 0.29853472
abbbc -0.15636028 -0.2953045 0.29853472

既然你在评论中说第一个字段可能包含数字,那么你不能使用搜索第一个浮点数的前瞻来解决问题。 (尽管如此,您仍然可以使用前瞻来计算行尾之前的浮点数,但它不是很方便)。

我建议的是基于 header 第一行定义的字段编号的解决方案。

您可以使用header行知道字段数并替换其他行开头的空格,直到字段数相同。

你可以像这样使用 perl 命令行作为 awk:

perl -MEnglish -pae'$c=scalar @F if ($NR==1);for($i=0;$i<scalar(@F)-$c;$i++){s/\s+/_/}' file

for 循环计算第一行(存储在 $c 中)和当前行(由 scalar(@F) 给出,其中 @F 是字段数组),并重复替换。

a 将 perl 命令行切换为自动拆分模式,-MEnglish 使 number row 变量可用作 $NR(类似于 NR 变量在 awk 中)。

可以这样缩短:

perl -pae'$c=@F if $.<2;$i=@F-$c;s/\s+/_/ while $i--' file

你的前瞻想法很好,但问题是如何只替换前瞻之前匹配的部分中的 space ,当它们与其他东西(即单词)混合时。

一种方法是捕获第一个浮点数之前的内容(由 lookahead 给出),并在替换部分 运行 捕获内容的另一个正则表达式,以替换 spaces

s{ (.*?) (?=\s+-?[0-9]+\.[0-9]) }{  =~ s/\s+/_/gr }ex

备注

  • 修饰符/e使替换部分被评估为代码;任何有效的 Perl 代码都可以

  • 使用 s{}{} 个分隔符,我们可以在替换部分的正则表达式中使用 s/// 个分隔符

  • 替换部分中的正则表达式,将捕获的文本中的 spaces 更改为 _,具有 /r 修饰符,因此 return修改后的字符串并保持原始不变。因此,我们不会尝试更改 </code>(它是只读的),并且修改后的字符串(被 returned)可用作替换 </p></li> <li><p>修饰符 <code>/x 允许在模式中使用 space,以提高可读性

  • 这里必须做一些假设。最关键的一点是要处理的文本后跟给定格式的数字 -?[0-9]+\.[0-9]+,而文本本身并没有这样的数字。这遵循了 OP 的样本,更明确地说,是尝试的解决方案

  • 几个带有假设的细节。 (1) [0-9]+\. 需要前导数字——如果您可以使用 .123 之类的数字,则使用 [0-9]*\. (2) 内部正则表达式中的 \s+ 折叠多个连续 spaces 变成一个 _,所以 a b c 变成 a_b_c(而不是 a__b_c

  • 在前瞻中,我用 \s+ 收集了第一个浮点数之前的所有 space——因此它们将留在第一个浮点数的前面。这是一个 space 想要的,但多个可能会很尴尬

    如果它们包含在 .*? 捕获中(如果前瞻只有一个 space、\s),那么我们会得到一个 _ 尾随单词(s).我认为那会 more 尴尬。理想的解决方案是 运行 另一个正则表达式并清理它,如果这种情况是可能的并且它很麻烦

一个例子

echo "a bbb c -0.15636028 -0.2953045" |
    perl -wpe's{(.*?)(?=\s+-?[0-9]+\.[0-9])}{  =~ s/\s+/_/gr }e'

打印

a_bbb_c -0.15636028 -0.2953045

然后要处理文件中的所有行,您可以执行以下任一操作

 perl -wpe'...' file > new_file

并得到一个 new_file 的变化,或者

 perl -i.bak -wpe'...' file

更改file 就地(即-i),其中.bak使其保存备份。

使用否定先行替换任何未跟随浮点数的空格:

echo "a bbb cc  -0.123232 -0.3232" | perl -wpe 's/ +(?! *-?\d+\.)/_/g'