regex 仅当组匹配时多行字符串替换

ljo96ir5  于 2023-10-22  发布在  其他
关注(0)|答案(1)|浏览(126)

只有当一个组匹配一个模式时,我才能对多行字符串执行一行单语句替换?
如果(类似YAML的文档的)“值”包含:或等于-,我需要引用它们。考虑下面的(非工作)代码:

$data =~ s/^(\s*\S+): (.+)$/$1: '$2'/mg if $2 =~ /:/ || $2 =~ /^\-$/;
  • 输入文本字符串示例 *
data:
        normal: text
        timestamp: Wed Aug 23 07:07:07 2023
        time-zone: UTC +03:00, Daylight Saving: +0h
        type: -
        duration: 45h 8m 41s
  • 期望输出 *
data:
        normal: text
        timestamp: 'Wed Aug 23 07:07:07 2023'
        time-zone: 'UTC +03:00, Daylight Saving: +0h'
        type: '-'
        duration: 45h 8m 41s
  • 工作代码 * -我想用一个更优雅的形式来替换
my @lines = split "\n", $data;
foreach my $i (0 .. $#lines) {
  my $line = $lines[$i];
  if ($line =~ /^(\s*\S+): (.+)$/) {
    my $key = $1;
    my $val = $2;
    $lines[$i] = "$key: '$val'" if $val =~ /:/ || $val =~ /^\-$/; # quote invalids
  }
}
$data = join "\n", @lines;
say $data;
8ftvxx2r

8ftvxx2r1#

perl -wnlE'say s/^.+?:\s\K (.*[:-].*)/\x27$1\x27/rx' file.txt

\K删除了所有之前的匹配(来自$&),所以它们留在字符串中,我们不必捕获它们并将它们放回去。\x27用于问题中使用的单引号。
/r修饰符让替换返回更改后的字符串(如果模式不匹配,则返回原始字符串),然后打印该字符串;原来的没有改变。参见perlre中的修饰符。可以将输出重定向到文件中,

perl -wnlE'...' file.txt > out.txt

也可以使用-i开关就地更改输入文件

perl -i.bak -wnlE'...' file.txt

.bak部分使它还可以使用该扩展名保存备份。请参见perlrun中的开关。
这假设感兴趣的模式总是包含在一行中。
我不确定是否有人会称之为“优雅”。
正如问题中所指出的,并在注解中澄清,输入是程序中的多行字符串,而不是文件。为了一次处理整个字符串,上面的正则表达式需要一个修改和不同的修饰符

use warnings;
use strict;
use feature 'say';

my $data = <<'EOF';
data:
        normal: text
        timestamp: Wed Aug 23 07:07:07 2023
        time-zone: UTC +03:00, Daylight Saving: +0h
        type: -
        duration: 45h 8m 41s
EOF

$data =~ s/^.+?:[\t ]+\K (.*[:-].*) $/'$1'/gmx;

say $data;

现在我们需要一个文字空间而不是\s(在第一个:之后),因为\s也匹配一个换行符,并且在第一行(data:后面没有任何东西)失败,使它向下搜索下一行。如果有文字空格,它就不能匹配新行,并将放弃第一行,从下一个^开始重新匹配。为了清楚起见,我喜欢用字符类来表示文字空间([ ]),然后再添加一个制表符,所以是[\t ]
这里我们还需要将模式限制在一条线上,否则贪婪的.*会吞下更多。使用/m修饰符,$(和^)应用于字符串内部的行(如果没有修饰符,它们只锚定整个字符串,而不是内部的行)。在这里,我们还需要/g来继续遍历字符串,进行更改。
在程序内部,单引号'不是问题,因为它们在命令行上,所以现在我们不需要为它们使用十六进制。
我用这里的文档来介绍多行字符串,用单引号,因为我们显然需要文字。
或者,为了避免这些微妙之处,仍然逐行处理,将字符串分成几行,对每一行运行regex,然后通过加入换行符(如果需要)重新组装。

$data = join "\n",
    map { s/^.+?:\s\K (.*[:-].*)$/'$1'/xr } 
    split /\n+/, $data;

这消除了可能的空行(在所示的样本数据中没有),因为在所有连续的\n上都有I split(带有+)。如果不希望这样,则使用split /\n/(不使用+),并保留空行。
如果不需要将其重新组合成多行字符串--或者无论如何都需要单独的行--那么将其赋值给一个数组(而不是join-ing并赋值回$data)。
现在,我们再次需要/r修饰符,以便map中的块返回(更改或原始)字符串,但不返回/g(也不返回/m)。

相关问题