使用Perl改善生活质量

2016-09-05 10:38:55来源:作者:Niche人点击

分享一下我日常使用Perl来改善生活质量的经历,主要是文本 /文件批处理,Perl的宇宙第一正则表达式不是盖的。

一、给自己的成人电影使用 base64加密

我的移动硬盘有一个文件夹全是成人影视,为了防止偶尔外借时别人乱入尴尬,我决定使用简单的 base64编码来加密,用Perl来实现就只有如下一行:

find . -type f | perl -pe 's|(.*)/([^.]+)(.*)|base64 @{[quotemeta $&]} > @{[quotemeta $1]}/@{[($cnt//=0)++]}$3.b64|g' | sh

讨论:

find命令来递归查找文件, -type f指定了只输出文件类型,从而保证文件夹等奇怪的东西不会被掺和进来。

稍有经验的人群会质疑,这里应该用 find . -type f -print0 | xargs -0来保证文件名里的空格字符不会引起奇怪的结果,一般来说这是对的,比如说:

find . -name '*.mp3' -type f -print0 | xargs -0 rm

如果写成

find . -name '*.mp3' -type f | args rm

那么在 mp3文件名包含空格 /n/t等时就会报错,但是我们将在Perl里对 /s进行处理,所以不必用 -print0 | xargs -0进行处理。

perl -pe来运行紧跟着的一行Perl代码并输出 $_到标准输出,重点是我们的Perl代码,使用 (.*)/([^.]+)(.*)把包含路径的文件名分割为 (路径)/(文件名)(.扩展名)。熟悉Python的玩家应该会意识到这在Python里其实就是:

$1 = os.path.dirname($_)$basename = os.path.basename($_)$2, $3 = os.path.splitext($basename)

不过我在这里让 $3匹配尽量多的后缀部分,这是考虑到类似 .file.py.swp之类的多重扩展名的文件,虽然我不认为我会在放成人影片的文件夹里写 vim。

接下来的替换其实就是执行 base64 input > output,不过需要注意三个地方,一个是我用 quotemeta函数处理 $&和 $1来规避文件名的空格字符,另一个是 ($cnt//=0)++会从 0开始自增作为输入文件的名字,再一个是 @{[code]}在 Perl qq字符串里内插时能够运行其中的代码,简直是好用到哭。

举个简单的例子,如果我们有个文件叫 ./s crawler /txt/some.txt,那么在这行Perl将输出:

base64 /.//s/ crawler/ //txt//some/.txt > /.//s/ crawler/ //txt/0.txt.b64

注意其中的转义字符和重定向输出的命名。

最后再用管道输出到 sh进行运行就 okay了!

而解码的代码也非常简单:

find . -name '*.b64' | perl -pe 's|(.*)/.b64|base64 -D @{[quotemeta $&]} > @{[quotemeta $1]}|' | sh

解码娱乐完之后再删除解码文件就行了:

find . ! -name '*.b64' -delete 二、让 vim运行部分代码

我在用 vim写博客的时候(比如现在)经常需要在插入代码段,最后我希望映射一个快捷键能够让我把可视模式下选中的文本中的Python代码运行一遍。

解决方案只需在 ~/.vimrc中定义可视映射:

vmap <c-c> :w !perl -ne 'BEGIN{print "/#coding=utf8/n"} print if /^/s*[a-z]/i' /| python<cr>

讨论:

我很早就定义了普通模式下的 <c-c>映射:

nmap <c-c> :w<cr> :w !python %<cr>

这样我在写Python脚本时按下 <c-c>就能执行当前脚本。但是很多时候我只需要执行部分代码,甚至说是从选出的文本段中抽出Python代码进行运行,所以我写出了上面的代码。

总的来说挺简单的,利用可视模式下的 :w !command命令把选择的文本段作为 command命令的标准输入,也就是后面的Perl代码,这段Perl挑选出以英文字母作为第一个非空字符的文本行并直径重定向给了Python解释器。

需要注意在一开始我先写入了 #coding=utf8,这样如果有中文的行间注释的话解释器也能正确运行。

其实一开始我使用了 vim的 global命令进行了尝试并写出了下面的代码:

vmap <c-c> :g//v^/s*[a-zA-Z]/.w! >> .tmp.py<cr> :!echo '/#coding=utf8/n' /| cat - .tmp.py /| python<cr>

利用 global命令选出匹配正则表达式的行,写入 .tmp.py文件,再把 #coding=uft8加入第一行,直接用管道扔给Python。

和Perl版本相比起来,这个版本除了需要敲打更多次数的键盘外,还有额外的 IO。 vim ex的细节之繁琐也令人发指,比如 #和 |必须进行转义,比如 .w! >> {file}这种语法不去查文档真的不太容易知道。讲道理的话既然我们有了Perl,那么直接把选择的文本段全部扔给Perl究竟哪里不好了?

三、格式化 Markdown

Markdown的格式要求真的很蛋疼。

比如说换行必须要使用空白行 /n/n、代码段必须缩进并且保持代码段前后有空白行、正文段的下划线需要进行转义、正文段的行间代码需要用反引号引用。

不过还好我有Perl。

插入空白行:

perl -pe 'print "/n" if /(?:^|[^#/x80-/xff])[/x80-/xff]/ && $last !~ /^/s+$/ || (//S/ && $last =~ /(?:^|^[^#/x80-/xff]+)[/x80-/xff]/); $last = $_;'

判断本行是否为文本行并且上行为空行、或者本行不是空行且上行是文本行。

关键在于三个正则表达式, /(?:^|[^#/x80-/xff])[/x80-/xff]/用来匹配文本行, /^/s+$/匹配空行, //S/匹配非空白行。

代码段的缩进:

perl -pe 's/^/ / if !/(?:^|[^#/x80-/xff])[/x80-/xff]/'

这个比较简单,先判断是否是代码行,再统一缩进 4空格就好。

文本行中的 /_/_转义:

perl -pe 's/__///_//_/ if !/(?:^|[^#/x80-/xff])[/x80-/xff]/'

讲道理的话不止 _需要转义,但是写Python的话就 /_/_遇到地比较多。

文本行中的代码用反引号引用:

perl -pe 's/(?>[^/x80-/xff/n]+)(?<!python)(?<!perl)/`$&`/gi if /(?:^|[^#/x80-/xff])[/x80-/xff]/'

重点在于固化分组和向后环视,Perl的后环视是无法匹配变长正则的,而匹配非中文字符串的固化分组也是必要的,否则会在回溯时匹配到 per和 pytho这种奇怪的东西上。

把上面的代码整合一下就是:

perl -i.bak -pe 'BEGIN{$txt = qr/(?:^|[^/#/x80-/xff])[/x80-/xff]/} print "/n" if $_ =~ $txt && $last !~ /^/s+$/ || (//S/ && $last =~ $txt); $last = $_; s/^/ / if $_ !~ $txt; s/__///_//_/ if $_ =~ $txt; s/(?>[^/x80-/xff/n]+)(?<!python)(?<!perl)/`$&`/gi if $_ =~ $txt;'

-i.bak将生成 .bak备份文件。

四、日志文件的查询

这是我下定决心学习Perl的直接动机:代替 awk。

我有一个日志文件,其有一部分的文件格式是这样的:

======================MYDATA: qid:1458277193672 req:BRU|PEK|20160505|qunarFlight slave_req:rtquery?content=BRU%26PEK%2620160505&source=qunarFlight&ticket_info=%7B%22flight_no%22%3A%22HU492%22%2C%22md5%22%3A%226e9482acd4b7ba921b2cbb79c5dcba7f%22%2C%22qid%22%3A%221458277193672%22%2C%22uid%22%3A%222650991395bd6bf748188fab5a870c52%22%7D type:flight res:[22, "REALTIME"] source:qunarFlight http_timeout:40s redis_timeout:1s dur_time:3s redis_key:BRU|PEK|20160505|qunarFlight refer_count:0 err_code:80 usr:mioji_validation passwd:1002c78ead5c05c1 level: rule:CN req_times:3 total_num:12 success_num:11 cand_source:orbitz ticket_info:{"flight_no":"HU492","md5":"6e9482acd4b7ba921b2cbb79c5dcba7f","qid":"1458277193672","uid":"2650991395bd6bf748188fab5a870c52"} thread1:rule=CN|res=[22, "REALTIME"]|err_code=80|dur_time=2054ms======================

现在我的任务是要找出指定的 source下的任务错误码 err_code为 80的 slave_req字段中的 content,而且要去重哦。

于是我写出了如下的代码:

perl -ne 'BEGIN{$/ = "=" x 22} next if !/source:qunarFlight/ || !/err_code:80/; ($dept, $dest, $date) = /content=([A-Z]{3})%26([A-Z]{3})%26([0-9]{8})/; $key = "$dept => $dest at $date/n"; print $key if !$memo{$key}++' 20160318_13.log

直接重定义 $/,然后对非指定 source和指定 err_code的段落跳过,用正则取出出发到达地点日期,拼出 key字符串,如果在 %memo哈希中没有的话则打印。

一气呵成。

事实上我利用Perl神奇的 ...操作符也可以实现:

perl -ne 'next unless /===/ ... /===/; ($dept, $dest, $date) = /content=([A-Z]{3})%26([A-Z]{3})%26([0-9]{8})/ if /slave_req/; ($source) = /:(/w+)/ if /source/; ($err) = /:(/d+)/ if /err_code/; $key = "$dept => $dest at $date/n"; print $key if $err == 80 && $source eq "qunarFlight" && $key && !$memo{$key}++; ' 20160318_13.log

两个代码得到的结果是一样的,但是第一段代码不仅更加节约键盘,而且比第二段代码快两倍以上。

所以说滥用 $/大法好!

而如果我坚持要用 awk写的话,我只能把 /===/作为 RS,再把 /n作为 FS,再数 slave_req是第几字段、 source是第几字段、 err_code是第几字段,但是我在 slave_req提取 content时还是会遇到困难,我忘了 sub能不能用正则取文本了,并且 awk奇怪的正则流派我真是非常嫌弃。

所以说还是Perl大法好!

简单写了写我平时使用Perl改善生活的 case,大概就如上四类:批处理文件、建立 vim映射、批处理文本、处理日志文件。用Perl解决问题真的是非常非常令人感到幸福啊!

以上。

(用Perl格式化去喽)

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台