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

multi-line string substitution only if group matches

提问人:h q 提问时间:9/3/2023 最后编辑:h q 更新时间:9/4/2023 访问量:84

问:

如何仅在组与模式匹配时才对多行字符串执行单行单语句替换?

我需要引用“值”(类似 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;
正则表达式 Perl

评论

0赞 zdim 9/3/2023
感兴趣的模式可以分布在行上,还是总是完全包含在一条线上?(换句话说,你为什么强调替换是在“多行”字符串上完成的?
0赞 h q 9/3/2023
我想我用错了术语。我的示例输入是带有换行符的单个字符串(不是文件)\r?\n

答:

2赞 zdim 9/3/2023 #1
 perl -wnlE'say s/^.+?:\s\K (.*[:-].*)/\x27$1\x27/rx' file.txt

\K 会删除所有以前的匹配项 (from ),因此它们会保留在字符串中,我们不必捕获它们并将它们放回去。是问题中使用的单引号。$&\x27

修饰符让替换返回更改后的字符串(如果模式不匹配,则返回原始字符串),然后打印该字符串;原件未更改。参见 perlre 中的修饰符。输出可以重定向到文件中,/r

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

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

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

该部分还使它使用该扩展名保存备份。请参阅 perlrun 中的开关.bak

这假设感兴趣的模式始终包含在一行中。

不知道是否会称它为“优雅”......


正如问题中指出的,并在注释中澄清,输入是程序中的多行字符串,而不是文件。为了一次处理整个字符串,上面的正则表达式需要一次更改和不同的修饰符

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:\sdata:^[ ][\t ]

在这里,我们还需要将模式限制在一条线上,否则贪婪的人会啜饮更多。使用修饰符时,(和)应用于字符串内的行(如果没有修饰符,它们仅锚定整个字符串,而不是内部的行)。在这里,我们还需要继续浏览字符串,进行更改。.*/m$^/g

在程序内部,单引号不是问题,因为它们在命令行上,所以现在我们不需要对它们使用十六进制。'

在这里使用 doc 来介绍多行字符串,带有单引号,因为我们显然想要文字文本。


或者,为了避免这些微妙之处,并且仍然逐行处理,将字符串分解为行,对每行运行正则表达式,然后通过连接换行符(如果需要)来重新组合

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

这消除了可能的空行(在显示的示例数据中没有空行),因为 I 在所有连续 (with ).如果这是不希望的,请使用(否)和空行。split\n+split /\n/+

如果不需要将其重新组合成多行字符串 - 或者无论如何都需要单独的行 - 则赋值给数组(而不是 -ing 并赋值回 )。join$data

现在我们再次需要修饰符,以便 in 中的块返回(更改的或原始的)字符串,而不是 (nor )。/rmap/g/m

评论

0赞 h q 9/3/2023
确实很优雅:-)虽然我无法让它在脚本中工作,但我得到:$data =~ s/^.+?:\s\K (.*[:-].*)/\x27$1\x27/rx;Useless use of non-destructive substitution (s///r) in void context at ./test.pl line XX.
1赞 Dave Cross 9/3/2023
@hq:选项 on 会更改其行为,因此它不会更改绑定的字符串,而是返回字符串的更改版本。在空洞的上下文中这样做是没有意义的。将结果分配给变量 () 或删除 ./rs///my $new_data = $data =~ s/.../.../r/r
0赞 h q 9/3/2023
再次感谢@DaveCross。我仍然无法在我的脚本中运行它:没有产生所需的结果。$data =~ s/^.+?:\s\K (.*[:-].*)/\x27$1\x27/x;
0赞 zdim 9/4/2023
@hq“没有产生预期的结果”——好吧,这是因为代码在整个多行字符串 (in ) 上运行,而它本来是逐行运行的。将修复为一分钟$data
0赞 zdim 9/4/2023
@hq 添加到末尾,现在应该可以在程序中使用多行字符串。为了清楚起见,还在其他地方进行了一些编辑(希望:)