我想编写一个 perl 单行代码来替换两个特定连续字符串的每个实例,这些字符串可能被空格分隔,也可能不被空格分隔。
例如,假设我的两个字符串是john paul
和 ,george
并且我想将这些字符串的连续实例(按此顺序)替换为pete
。运行单行代码
$ cat ~/foo
john paulgeorge
john paul george
john paul
george
george john paul
应该导致
$ cat ~/foo
pete
pete
pete
george john paul
我唯一想到的是
$ perl -p -i -e 's/john paul\s*george/pete/g' ~/foo
但这会导致
$ cat ~/foo
pete
pete
john paul
george
george john paul
有没有办法改变我的一句台词?
答案1
您需要添加到单行代码中的唯一一件事是将文件作为单个字符串进行读取的选项:
perl -0777 -p -i -e 's/john paul\s*george/pete/g' ~/foo
# ^^^^^
答案2
perl
-n
和选项-p
在您的程序周围放置了变体while (<>) { ... }
,这使得它们逐行处理输入。如果要跨多行进行替换,则需要将整个内容读入字符串中,这需要您自己完成。
perl -e 'local $/;$_=<>;s/john paul\s*george/pete/g;print'
这未定义$/
, 记录分隔符, 以便<>
吸食不再进行行分割,$_
立即读取整个输入,然后对该长字符串进行替换。您也必须自己打印。
这里不再有太多魔力了——它只是以一种有点不舒服的方式编写一个完整的 Perl 程序。-i
不过,仍可用于就地更换。
如果您有一个大文件,这将相当低效(或耗尽您的内存),但如果不构建更好的解析器,这似乎或多或少是不可避免的。您还可以查看perldoc -q 'entire file'
其他替代方案,并且很多人告诉您您并不是真正的意思。
答案3
您可以在sed
不占用整个文件的情况下执行此操作:
sed -e ':top' -e 's/john paul[[:space:]]*george/pete/g;$b' -e '/john paul[[:space:]]*$/!b' -e 'N;btop' input
这对内存使用来说要轻得多;当存在从当前行开始的多行匹配的可能性时,它仅吸收多行。然后它只会吸食直到找到匹配,或者直到不再有匹配的可能性。
另外,它还符合 POSIX 标准。 (Perl 不是 POSIX 的一部分。)感谢 mikeserv 在评论中指出了这一点。
解释:
:top
设置一个名为 的标签top
。
s/john paul[[:space:]]*george/pete/g
对模式空间中的任何内容进行所需的替换。 (默认为逐行。)
$b
如果当前行是文件的最后一行,则跳到末尾并打印。
/john paul[[:space:]]*$/!b
:
该模式将在模式空间的末尾/john paul[[:space:]]*$/
匹配,后跟任意数量的空格(但除了空格之外什么都没有),然后反转模式。所以这里的效果是仅当不存在多重的可能性时才执行命令(跳到脚本的末尾,从而打印模式空间,从文件中读取下一行,并从脚本的顶部开始) - 从当前模式空间开始的行匹配。john paul
!
b
N
将文件中的下一行追加到模式空间(追加换行符后)。
btop
分支到:top
标签而不清除模式空间。
答案4
您需要使用选项 -0777 来读取文件。但您还应该在末尾添加 m 修饰符,以确保 \s 也与 \n 匹配。
当 Perl 看到 -0 时,它将用接下来的内容更新输入记录分隔符 ($/)。例如,如果我输入 -00,Perl 会将 $/ 置于段落模式中。所以
perl -0777 -pe 's/^john paul\s*george/pete/gm' george.txt
相当于:
perl -pe 'BEGIN { undef $/ ; } s/^john paul\s*george/pete/gm' george.txt