【问题标题】:substituting spaces for underscores using lookaheads in perl在 perl 中使用前瞻替换下划线的空格
【发布时间】:2019-09-13 18:52:58
【问题描述】:

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

字 -0.15636028 -0.2953045 0.29853472 ....

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

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

a bbb c -0.15636028 -0.2953045 0.29853472 ....(数百个浮点数)

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

a_bbb_c -0.15636028 -0.2953045 0.29853472 ....(几百个浮点数)

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

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

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

【问题讨论】:

  • 浮动列的数量是固定的吗? “a bbb c”可以包含数字吗?
  • 是的,它也可以包含数字。
  • ...浮动的数量可能会有所不同,但对于任何处理的文件都是相同的。
  • 文件中的所有行是否总是相同的(即一个单词后跟浮点数)?是否有标题第一行(带有列名),您可以从中推断出列数?
  • 是的,有这样一个header。我知道预期的花车数量。

标签: perl regex-lookarounds substitution


【解决方案1】:

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

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

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

注意事项

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

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

  • 替换部分中的正则表达式,在捕获的文本中将空格更改为_,具有/r 修饰符,以便返回修改后的字符串并保持原始字符串不变。因此我们不会尝试更改$1(它是只读的),并且修改后的字符串(正在返回)可用作替换

  • 修饰符 /x 允许在模式中使用空格,以提高可读性

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

  • 一些带有假设的细节。 (1) [0-9]+\. 应为前导数字 - 如果您可以有 .123 这样的数字,则使用 [0-9]*\. (2) 内部正则表达式中的 \s+ 将多个连续空格折叠成一个 _,所以 @ 987654335@ 变为 a_b_c(而不是 a__b_c

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

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

一个例子

echo "a bbb c -0.15636028 -0.2953045" |
    perl -wpe's{(.*?)(?=\s+-?[0-9]+\.[0-9])}{ $1 =~ 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 使其保存备份。

【讨论】:

  • 感谢您提供非常好的答案。以为我对正则表达式和替换有很好的控制,让我再想一想:-)。而且,不用说,这确实有效......
  • @user3422424 太好了,如果有帮助我很高兴。我希望这不是太多的“解释” :) 让我知道是否有问题,或者是否随时弹出。
  • 哇,有-1(不知道为什么),现在是-2 ...?认真的吗?
  • 不错的答案。我不知道如何将我的两个正则表达式合二为一。现在我愿意。 +1
  • @Andrey 谢谢。是的,s///e 是两步处理的便捷方式——适当地捕获然后在其上运行代码。 (但应该记住不要太过分!:)
【解决方案2】:

这样的东西对你有用吗:

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

【讨论】:

    【解决方案3】:

    使用负前瞻替换任何不跟浮点数的空格:

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

    【讨论】:

      【解决方案4】:

      假设您的 cmets 文件看起来像这样:

      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
      

      由于您在 cmets 中说过第一个字段可能包含数字,因此您不能使用先行搜索第一个浮点数来解决问题。 (不过,您可以使用前瞻来计算直到行尾的浮点数,但这不是很方便)。

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

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

      你可以像 awk 那样使用 perl 命令行:

      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 可用(就像 awk 中的 NR 变量)。

      可以这样缩短:

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

      【讨论】:

      • @ikegami:请注意,我已经在 cmets 中询问了文件开头是否有标题行,以及每个文件的每一行是否具有相同数量的字段。答案都是肯定的。关于最终的连续空格,它确实是一个细节,不会出现在示例字符串中,而且很容易解决。
      • @ikegami:关于否决票我想那些无法完全阅读答案的人已经用示例行测试了命令行,却不明白需要标题行才能使其工作。
      • 所以在最坏的情况下,它假设标题行的格式也不正确?在这种情况下,user3422424 如何说“我知道预期的浮点数”?他发明了它还是他宣布了它?不管它是比前瞻更好的解决方案,因为第一个字段可以包含数字。
      • "不止一次空格",也就是第一个字段的连续空格:只需要加上+量词就可以解决了。
      • 这不是问题或在示例字符串中,但为什么不是。
      猜你喜欢
      • 2011-07-12
      • 1970-01-01
      • 2020-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 1970-01-01
      相关资源
      最近更新 更多