Perl / Unix 一行脚本对决,第一部分

shell(如Bash)提供了内置命令和脚本功能,可以轻松解决和自动化各种任务。grep、sed、Awk、sort、find或parallel等外部命令可以组合在一起协同工作。有时,您可以使用Perl作为单一替代品或特定用例的补充。
Perl是满足文本处理需求的最高效、最便携的选项。Perl具有功能丰富的正则表达式引擎、内置函数、广泛的生态系统,并且相当便携。然而,与专用工具相比,Perl可能性能较慢,并且可能更冗长。
一行脚本还是脚本?
为了对数字信号处理(DSP)芯片进行汇编级测试,我需要为多个地址范围复制相同的场景。当时我对Linux命令行的了解有限,不知道如何使用sed或Awk。我使用Vim和Perl来处理各种文本处理需求。
我不知道Perl的一行脚本选项,所以我每次需要为多个文件进行替换时,都会修改脚本。有一次,我甚至将文件作为Vim缓冲区打开,并应用bufdo
命令,看看这会使我的工作流程更简单。如果我知道Perl的一行脚本,我可以轻松利用find和Bash通配符使我的生活变得更简单,例如
$ perl -i -pe 's/0xABCD;/0x1234;/; s/0xDEAD;/0xBEEF;/' *.tests
-i
选项会将更改写回源文件。如果需要,我可以传递一个参数来创建原始文件的备份。例如,-i.bkp
将为传递作为输入文件的ip.txt创建备份ip.txt.bkp。我还可以将备份放在另一个现有目录中。通配符*
将被扩展为原始文件名
$ mkdir backups
$ perl -i'backups/*' -pe 's/SEARCH/REPLACE/g' *.txt
强大的正则表达式功能
Perl的正则表达式比工具程序使用的基本或扩展正则表达式更强大。我经常使用的常见功能是非贪婪和占有性量词、前瞻、/e
标志、子表达式调用和(*SKIP)(*FAIL)
。以下是多年来我回答StackOverflow帖子中的某些示例。
跳过一些匹配项
这个问题需要将avr-asm转换为arm-gnu注释。起始文件看起来像这样
ABC r1,';'
ABC r1,";" ; comment
;;;
我需要将;
更改为@
,但单引号或双引号内的;
不应受到影响。我可以匹配选择支的第一分支中的引号内的;
,并使用(*SKIP)(*F)
来不替换这些
$ perl -pe 's/(?:\x27;\x27|";")(*SKIP)(*F)|;/@/' ip.txt
ABC r1,';'
ABC r1,";" @ comment
@;;
我经常使用(*SKIP)(*F)
,所以我希望它有一个更短的语法,例如(*SF)
。
使用递增值替换字符串
我可以使用递增值替换字符串。替换中的/e
允许我将替换侧视为Perl代码。该代码评估的结果是替换内容。这可能是一个我递增的变量
$ echo 'a | a | a | a | a | a | a | a' | perl -pe 's/ *\| */$i++/ge'
a0a1a2a3a4a5a6a
反转子字符串
我还使用/e
技巧反转与模式匹配的文本
$ echo 'romarana789:qwerty12543' | perl -pe 's/\d+$/reverse $&/e'
romarana789:qwerty34521
进行一些算术运算
将另一个/e
改为/ee
意味着有两轮Perl代码。我将替换侧评估为字符串,然后将其评估为Perl代码。在文本文件中的算术替换中,我需要找到简单的算术,如25100+10
,并将其替换为其算术结果
id=25100+10
xyz=1+
abc=123456
conf_string=LMN,J,IP,25100+1,0,3,1
我可以通过匹配数字并在替换侧进行一些Perl操作,用一行/e
做到这一点
$ perl -pe 's/(\d+)\+(\d+)/$1+$2/ge' ip.txt
id=25110
xyz=1+
abc=123456
conf_string=LMN,J,IP,25101,0,3,1
但是,而不是分别匹配数字,我可以匹配整个表达式。匹配结果是 $&
,所以第一个 /e
将其插值到 25100+10
。第二轮运行的是 Perl,这是加法。
$ perl -pe 's/\d+\+\d+/$&/gee' ip.txt
id=25110
xyz=1+
abc=123456
conf_string=LMN,J,IP,25101,0,3,1
这也会使处理一组运算符变得更容易。
$ echo '2+3 10-3 8*8 11/5' | perl -pe 's|\d+[+/*-]\d+|$&|gee'
5 7 64 2.2
处理换行符。
我想取消这个文本的格式。
Hello there.
It will rain to-
day. Have a safe
and pleasant jou-
rney.
与 sed 和 Awk 不同,您可以选择在 Perl 中保留记录分隔符。这使得解决这个问题变得更容易。
$ perl -pe 's/-\n//' msg.txt
Hello there.
It will rain today. Have a safe
and pleasant journey.
请参阅 删除破折号并将换行符替换为空格,了解类似的问题,并比较 Perl 解决方案与 sed/Awk。
多行固定字符串替换。
使用 Perl 内置功能转义正则表达式元字符更简单。结合将整个输入文件作为单个字符串“吸入”,我可以轻松执行多行固定字符串替换。考虑以下示例输入。
This is a multiline
sample input with lots
of special characters
like . () * [] $ {}
^ + ? \ and ' and so on.
假设你有一个包含你希望匹配的行的文件。
like . () * [] $ {}
^ + ? \ and ' and so on.
还有一个包含替换字符串的文件。
---------------------
$& = $1 + $2 / 3 \ 4
=====================
以下是使用 Perl 实现此方法的一种方法。
$ perl -0777 -ne '$#ARGV==1 ? $s=$_ : $#ARGV==0 ? $r=$_ :
print s/\Q$s/$r/gr' search.txt replace.txt ip.txt
This is a multiline
sample input with lots
of special characters
---------------------
$& = $1 + $2 / 3 \ 4
=====================
请注意,在上述解决方案中,search.txt
和 replace.txt
的内容也由 Perl 命令处理。避免使用 shell 变量来保存它们的内容,因为尾随换行符和 ASCII NUL 字符需要特别注意。
Awk 和 sed 没有等效选项来“吸入”整个输入文件内容。Sed 是图灵完备的,Awk 是一种编程语言,因此如果您愿意,可以为其编写代码,除了您需要用于转义元字符的代码之外。
更好的正则表达式支持。
一些其他正则表达式库可能存在与它们实现方式相关的问题。例如,GNU 版本可能存在一些其他实现可能不存在的错误。您使用的版本可能会产生不同的结果。然而,Perl 在任何地方都有相同的错误。
后向引用。
我发现了一个关于 glibc 中的后向引用的问题,并已在 grep 中进行了报告。这个问题在至少 GNU 实现的 grep 和 sed 中可以看到。据我所知,没有任何 Awk 实现支持正则表达式定义中的后向引用。
我想获取具有两个连续重复字符出现的单词。这个例子需要一些时间,但没有输出。
$ grep -xiE '([a-z]*([a-z])\2[a-z]*){2}' /usr/share/dict/words
当展开嵌套或使用 PCRE 时,它确实工作。
$ grep -xiE '[a-z]*([a-z])\1[a-z]*([a-z])\2[a-z]*' /usr/share/dict/words
Abbott
Annabelle
...
$ grep -xiP '([a-z]*([a-z])\2[a-z]*){2}' /usr/share/dict/words
Abbott
Annabelle
...
以下是 Perl,这是原始的正则表达式。
$ perl -ne 'print if /^([a-z]*([a-z])\2[a-z]*){2}$/i' /usr/share/dict/words
Abbott
Annabelle
...
单词边界。
为什么这个 sed 命令不替换倒数第三个“and”? 展示了当涉及到单词边界和组重复时另一个有趣的错误。这个错误在 Linux 上使用 glibc 的正则表达式功能(如)的任何东西中都可以看到。
这会错误地匹配,因为在“cocoa”的中间没有单词边界。
$ sed --version
sed (GNU sed) 4.8
$ echo 'cocoa' | sed -nE '/(\bco){2}/p'
cocoa
没有量词,就没有问题,也没有匹配。
$ echo 'cocoa' | sed -nE '/\bco\bco/p'
$ echo 'cocoa' | perl -ne 'print if /(\bco){2}/'
以下是 GNU sed 的另一个示例。它修改了这一行,因为它认为它在“with”之后找到了“it”作为一个单独的词两次,但第二个实际上在“sit”的中间。
$ echo 'it line with it here sit too' | sed -E 's/with(.*\bit\b){2}/XYZ/'
it line XYZ too
将模式更改为消除量词,它就可以正常工作了。
$ echo 'it line with it here sit too' | sed -E 's/with.*\bit\b.*\bit\b/XYZ/'
it line with it here sit too
$ echo 'it line with it here sit too it a' | sed -E 's/with.*\bit\b.*\bit\b/XYZ/'
it line XYZ a
# Perl doesn't need such workarounds
$ echo 'it line with it here sit too' | perl -pe 's/with(.*\bit\b){2}/XYZ/'
it line with it here sit too
$ echo 'it line with it here sit too it a' | perl -pe 's/with(.*\bit\b){2}/XYZ/'
it line XYZ a
敬请期待。
在第二部分中,我将深入了解 XML、JSON 和 CSV,届时我将有更多内容。
其他阅读内容。
Dave Cross 的 Perl 命令行选项。
将文件读入 shell 变量的陷阱 和我的关于 使用 cli 工具进行多行固定字符串搜索和替换 的博客文章。
在 GNU grep 手册 中的已知错误部分。
图片来自 Flickr 上的 Dim Sum! (CC BY-NC-ND 2.0)
标签
反馈
这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。