使用Perl正则表达式替换分组

4

I have the following JSON input

... "somefield":"somevalue", "time":"timevalue", "anotherfield":"value" ...

我正在我的 KornShell (ksh) 脚本中,希望用我的值替换 timevalue。因此,我创建了这个正则表达式,使用组来工作得很好。

data=`cat somefile.json`
echo $data | perl -pe "s|(.*time\"\s*\:\s*\").*?(\".*)|\1%TIME%\2|g" | another-script.sh

... "somefield":"somevalue", "time":"%TIME%", "anotherfield":"value" ...

然而,我不能使用数字作为替换,因为Perl使用数字来定义组,所以这个显然不起作用:

perl -pe "s|(.*time\"\s*\:\s*\").*?(\".*)|\120:00:00\2|g"

我可以通过进行两步替换来解决这个问题,

perl -pe "s|(.*time\"\s*\:\s*\").*?(\".*)|\1%TIME%\2|g" | perl -pe "s|%TIME%|20:00:00|"

... "somefield":"somevalue", "time":"20:00:00", "anotherfield":"value" ...

但我相信有更好、更优雅的方法来实现它。


5
更好更优雅的方式是使用JSON解析器... - Matt Jacob
2个回答

9

Perl在替换中不使用\1。如果你启用了警告(例如,使用perl -w),Perl会告诉你应该使用$1。通过添加{}可以将其与周围的数字区分开来:

perl -pe 's|(.*time"\s*:\s*").*?(".*)|${1}20:00:00$2|g'

我已经从正则表达式中删除了所有多余的反斜杠。另外,如果你只是将它替换为自己,那么匹配 .* 有什么意义呢?难道不能简单地写成
perl -pe 's|(time"\s*:\s*").*?(")|${1}20:00:00$2|g'

我不是很喜欢使用.*.*?。如果你想匹配引号内的内容,最好是具体一些:

perl -pe 's|(time"\s*:\s*")[^"]*(")|${1}20:00:00$2|g'

我们不需要验证输入字符串,因此现在没有理由匹配最后的 "(并将其替换为它本身):
perl -pe 's|(time"\s*:\s*")[^"]*|${1}20:00:00|g'

如果您的Perl版本不古老(5.10+),您可以使用\K来“保留”字符串的前导部分,即在匹配中不包括它:
perl -pe 's|time"\s*:\s*"\K[^"]*|20:00:00|g'

现在只有[^"]*部分将被替换,省去了我们进行任何捕获的步骤。

这太棒了!我起初尝试使用 $1,但是我错过了转义,然后改用了 \1(现在我明白是错误的)。如果不转义,它对我来说无法工作。perl -w -pe "s|(.time"\s:\s").?(".*)|${1}20:00:00${2}|g"。你最后的解决方案使用 \K 真的很棒,我一定会使用这个。非常感谢! - Nir
2
@Nir 整个问题在于你在代码周围使用了双引号。当有疑问时,在 shell 中始终使用单引号(除非你想要插入变量)。 - melpomene

7

虽然你可以使用正则表达式来实现这个功能,但使用正确的工具会更加容易。

jq '.time="20:00:00"' somefile.json 

如果您特别希望使用Perl,自2011年起,核心Perl发行版已经包含了JSON解析器,因此您可以这样做:

perl -MJSON::PP=decode_json,encode_json -0 -E '$j = decode_json(<>); $j->{time} = "20:00:00"; say encode_json($j)' somefile.json

1
应该是-0777,而不是-0(假设您想要读取整个输入)。-0会将输入行终止符设置为"\0",而不是undef - melpomene
1
此外,为了简洁起见,可以使用 perl -MJSON::PP -p0777 -e '$_ = encode_json { %{decode_json $_}, time => "20:00:00" } - melpomene
谢谢,我猜测我的版本不支持这个“Can't locate JSON/PP.pm in @INC”。我没有权限更新或安装任何东西,所以我不能使用jq。 - Nir
你使用的是基于RedHat的发行版吗?其中一些以令人讨厌的方式拆分标准Perl包。通常有一个名为perl-full或类似命名的软件包,可以提供所有内容。自Perl 5.14以来,JSON::PP已成为标准。 - Grant McLean
如果我没记错的话,在旧版的 RedHat 上是 perl-core - melpomene

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