perl-one-lines

来源:互联网 发布:win8映射网络驱动器 编辑:程序博客网 时间:2024/06/04 19:52
概要
本文包含一些perl中常见的命令行选项,也就是常说的one-liners。包含了-e,-n,-p,-M,-w选项,以及BEGIN和END。

1. 创建一个one-liner
我遇到的很多有天分的unix程序员都有一些必杀技。在遇到可以用Perl one-liner取代一个完整的脚本时,他们肯定不会写脚本。

-e开关允许我们在命令行中运行脚本。Code Listing1展示了一个简单的“Hello world”。

-----------------Code Listing1:Hello world--------------------------
prompt$ perl -e ’print "hello world!\n"’
hello world!
---------------------------------------------------------------------

Code Listing2,有一点复杂。从ls中获得输入,找到文件大小的字段,计算所有文件大小的总和。
-----------------Code Listing2:File Size Sum-------------------------
prompt$ ls -lAF | perl -e ’while (<>) { next if /^dt/; $sum += (split)[4] } print "$sum\n"’
1185
---------------------------------------------------------------------
在Code listing2中,我用了几个小技巧。通常情况下,我不会一次写好这样的东西。我一次写一点,确保我每次都会得到正确的结果。
在Code listing3中,我检查输出命令。

-----------------Code Listing3:ls output----------------------------
prompt$ ls -lAF
total 32
drwxrwsr-x 2 jbay staff 512 Feb 21 09:34 adir/
-rw-rw-r-- 1 jbay staff 395 Feb 21 09:29 afile1
-rw-rw-r-- 1 jbay staff 423 Feb 21 09:29 afile2
-rw-rw-r-- 1 jbay staff 120 Feb 21 09:29 afile3
---------------------------------------------------------------------
在Code Listing4中,ls的输出变成我的脚本的标准输入。ls的输出是每行一个。
我所期望的输出与Code Listing3是一样的。

-----------------Code Listing4:ls piped to perl---------------------
prompt$ ls -lAF | perl -e ’while (<>) { print $_ }’
total 32
drwxrwsr-x 2 jbay staff 512 Feb 21 09:34 adir/
-rw-rw-r-- 1 jbay staff 395 Feb 21 09:29 afile1
-rw-rw-r-- 1 jbay staff 423 Feb 21 09:29 afile2
-rw-rw-r-- 1 jbay staff 120 Feb 21 09:29 afile3
---------------------------------------------------------------------
在Code List5中,我想跳过“total”行和文件夹,所以我想忽略以‘t’和‘d’开头的行,在显示结果之前我添加了“next”,来跳过以‘t’和‘d’开头的行。

-----------------Code Listing5:Skip Lines---------------------------
prompt$ ls -lAF | perl -e ’while (<>) { next if /^[dt]/; print $_; }’
-rw-rw-r-- 1 jbay staff 395 Feb 21 09:29 afile1
-rw-rw-r-- 1 jbay staff 423 Feb 21 09:29 afile2
-rw-rw-r-- 1 jbay staff 120 Feb 21 09:29 afile3
---------------------------------------------------------------------

在我知道我的程序运行正确之后,我要获取每一行第五列的结果。这时,在Code Listing6中,输出上面与上面ls命令相同的结果-每一个文件的大小。
-----------------Code Listing6:Print File Size-----------------------
prompt$ ls -lAF | perl -e ’while (<>) { next if /^[dt]/; print +(split)[4], "\n" } ’
395
423
120
---------------------------------------------------------------------

最后,输出文件大小的总和,Code Listing7,累加文件大小总和,在程序最后输出。

-----------------Code Listing7:Sum File Size------------------------
prompt$ ls -lAF | perl -e ’while (<>) { next if /^[dt]/; $sum += (split)[4] } print "$sum\n"’
938
---------------------------------------------------------------------
现在,我获得了在Code Listing2中的perl one-liner。

2.one-liner输入
perl程序能够从标准输入或者命令行参数(保存在@ARGV)中获得输入。

2.1标准输入

-----------------Code Listing8:Skip Comment-------------------------
prompt$ cat afile | perl -e ’while (<>) { print unless /\s+#/ }’
---------------------------------------------------------------------

“|”把cat命令的输出转换成perl程序的输入。钻石操作符“<>”从标准输入读取数据,所以这个程序从afile
读取数据,然后打印不匹配正则表达式\s+#的行。
同样也可以通过shell重定向操作符<来重把文件内容重定向到perl的标准输入。
Code Listing9显示和前一个例子相同的输出。

-----------------Code Listing9:Input By Redirection-----------------
prompt$ perl -e ’while (<>) { print unless /\s+#/ }’ < afile
---------------------------------------------------------------------

不论如何,钻石操作符能够直接打开以及读取命令行指定文件的内容,所以不需要手动重定向文件内容。Code Listing10不用重定向,
并且得到Code Listing9相同的结果。

-----------------Code Listing10:Input-------------------------------
prompt$ perl -e ’while (<>) { print unless /\s+#/ }’ afile
---------------------------------------------------------------------

2.2命令行参数
可以通过@ARGV获得命令行参数,Code Listing11简单的打印@ARGV中的内容。
-----------------Code Listing11:Print The Command Line argument-----
prompt$ perl -e ’print "@ARGV\n"’ Foo Bar Bletch
Foo Bar Bletch
---------------------------------------------------------------------

假如我有一个文件,他的内容是一些我想操作的文件的名字,每行一个。我可以同Code Listing12来获得这些文件的名字。
-----------------Code Listing12:The Filename in files.txt-----------
prompt$ cat files.txt
afile1
afile2
afile3
---------------------------------------------------------------------

Unix的xarg命令能够把他读取的数据转换成另一个命令的参数。我想把这些文件名转换成wc命令的参数以便于统计每个文件中行数。
在Code Listing中,xarg从标准输入读取数据-文件名,并把他们转换成wc的参数。
-----------------Code Listing13:Count Lines In Files----------------
prompt$ cat files.txt | xargs wc -l
54 afile1
54 afile2
54 afile3
162 total
---------------------------------------------------------------------

Code Listing13和直接使用命令行参数的结果是一样的。如同Code Listing14
-----------------Code Listing1:Hello world--------------------------
prompt$ wc -l afile1 afile2 afile3
---------------------------------------------------------------------

2.3使用find
在Code Line15中,我用perl one-liner和xarg来重新实现了find的“-type d”选项。find命令递归一个指定给定的文件夹,根据一个标准,
输出匹配的一组文件名。
-----------------Code Listing15:Using find--------------------------
prompt$ find . | xargs perl -e ’@ARGV = grep( -d $_ , @ARGV); print "@ARGV"’
---------------------------------------------------------------------
在Code Listing中,xarg接受文件名,并把他们作为参数传给perl one-liner。one-liner使用grep过滤文件,然后打印他们。

3 使用命令行开关
Perl命令行选项通过使用-e选项自动处理小的脚本来缩短one-liners。Perl还有许多其他的有用的选项,perlrun来学习细节。

3.1 -e选项
perl解释器把-e选项当做一段代码,并且执行它。命令行中的每一个-e选项当做一行代码,如果把-e选项的内容粘贴到文件中,并且用perl来
执行那个文件,将会得到和-e选项相同的结果。Code Listing16通过两个-e选项把Code Listing1重写一遍。
-----------------Code Listing16:Multiple -e Switches----------------
prompt$ perl -e ’print "Hello ";’ -e ’print "world\n";’
Hello world
---------------------------------------------------------------------

每一个代码单元[原文为code bit](外部单引号之间),在shell中被匹配成单独的标记(token),所以shell可以看见四个标记(token)在
Code Listing17中
-----------------Code Listing17:Multiple -e swithes,as tokens------
-e
print "Hello ";
-e
print "world\n";
---------------------------------------------------------------------

3.2 -n选项
-n选项在你的程序中封装一个while循环,在Code Listing18中,loop循环通过钻石操作符读取输入,把$_设置为读取的内容,
然后用-e选项执行代码单元。
-----------------Code Listing18:Using -n---------------------------
while (<>) {
<-e argument>
<-e argument>
}
---------------------------------------------------------------------

在Code Listing中,重写了cat命令
-----------------Code Listing19:Reimplenmenting cat-----------------
prompt$ perl -ne ’print $_’ afile
---------------------------------------------------------------------

3.3 -p选项
-p选项做同样的事,并且每次递归后输出$_的值
-----------------Code Listing20:Using -p----------------------------
while (<>) {
<-e argument>
<-e argument>
print;
}
---------------------------------------------------------------------

在Code Listing20中,循环从输入中读取数据,把读取内容赋值给$_,执行-e选项,输出$_内容,可以通过这个来修改
一个输出中的内容
例如,可以删除“ls -l”输出文件列表中关于权限的那一列。在Code Listing21中,删除了第一个非空字段以及该字段以后的空格。

-----------------Code Listing21:Remove The First Column-------------
prompt$ ls -l | perl -pe ’s/\S+ //’
---------------------------------------------------------------------

3.4使用模块
通过-M开关,可以再命令行中使用模块。-M<Module>开关等同于在“虚拟脚本”中使用了“use Module”。在Code Listing22中,
使用了IO::Handle模块启动标准输出模块的自动更新。

-----------------Code Listing22:Using Module------------------------
prompt$ cat afile | perl -MIO::Handle -e ’STDOUT->autoflush(1); while (<>) { print }’
---------------------------------------------------------------------

通常,在我的脚本中,我使用strict和warnings。而且这个也可以在one-liner中实现,在Code Listing23中,
我使用了strict,并且通过-w来启动警告开关。
-----------------Code Listing23:Using Strict------------------------
prompt$ cat afile | perl -w -Mstrict -e ’my $var = 17; print $var’
---------------------------------------------------------------------

如果我没定义$var,strict模块如同在Code List24中那样捕获它,处理它。
-----------------Code Listing24:Undeclared Variables----------------
prompt$ cat afile | perl -w -Mstrict -e ’$var = 17; print $var’
Global symbol "$var" requires explicit package name at -e line 1.
Execution of -e aborted due to compilation errors.
---------------------------------------------------------------------

在Code List25中,Perl警告我使用没有初始化的变量。
-----------------Code Listing25:Uninitialized Variables-------------
cat afile | perl -w -Mstrict -e ’my $var; print $var’
Use of uninitialized value at -e line 1.
---------------------------------------------------------------------

4与shell混合使用
单双引号,如同美刀符号,都是shell的词法,如果在字符串中需要使用他们,那么必须让程序识别它们。
每个shell都有它特别的符号,不同的平台的处理方式可能不同。Code Listing26展示了处理特殊的shell元字符
的例子。

-----------------Code Listing26:Escaping Shell Metacharactors-------
prompt$ echo "the variable \$USER is "\""$USER"\"" "
the variable $USER is "jbay"
---------------------------------------------------------------------

有几种方法可以避免shell的引用问题。在Code Listing27中,程序输出错误的SQL语句,
因为a3没有被引号括起来。单引号消失了,因为我用单引号来引用我得代码单元,但是我需要
在a3两侧加上引号,来使SQL知道a3是字符串而不是列的名字。

-----------------Code Listing27:Misquoted SQL-----------------------
prompt$ perl -e ’print "select * from foo where bar=’a3’\n"’
select * from foo where bar = a3
---------------------------------------------------------------------

在Code Listing28中,我使用perl的chr()函数,通过使用它的ascii值来添加任意字符(包括单引号)。
可以把chr(39)和其他的SQL语句组合。

-----------------Code Listing28:Using Chr() To Get Literal Values---
prompt$ perl -e ’print "select * from foo where bar=" . chr(39) . "a3" . chr(39) . "\n"’
select * from foo where bar=’a3’
---------------------------------------------------------------------

在Code Listing29中,使用了通用引号操作符,q和qq来替代单双引号。我可以在perl的其他的串中使用单双引号
因为他们不再是边界定义符号。

-----------------Code Listing29:Generalized Quotes------------------
prompt$ perl -e ’print qq#select * from foo where bar="a3"\n#’
select * from foo where bar="a3"
---------------------------------------------------------------------

在Code Listing30中,使用了反斜杠“\”,但是语法并不是很诡异。在大多数的shell中,我使用第一个技巧来封装
优先的字符串,然后在加上“\”,最后使用第三个技巧。最后在执行之前组合它们。
-----------------Code Listing30:Escaping Quote Characters-----------
prompt$ perl -e ’print "select count(*) from foo where bar =’\’’a3’\’’\n"’
select * from foo where bar =’a3’
---------------------------------------------------------------------

5开始和结束的技巧
通过BEGIN和END关键字我可以再我得-e程序开始之前或结束之后执行代码。Code Listing31使用END在while循环结束后
输出文件总和。
-----------------Code Listing31:End Block---------------------------
ls -lAF | perl -ne ’next if /^d/; $sum += (split)[4]; END{ print "$sum\n" }’
---------------------------------------------------------------------

在Code Listing32中,程序在执行之前先执行BEGIN代码段。如果使用BEGIN块,可以在循环执行前把
$sum变量初始化为1024。
-----------------Code Listing32:Begin Block-------------------------
ls -lAF | perl -ne ’BEGIN{$sum=1024} next if /^d/; $sum += (split)[4]; END{ print "$sum\n" }’
---------------------------------------------------------------------

6 参考资料:
第六章, “Social Engineering, Cooperating with Command Interpreters”, Programming Perl - Larry Wall,Tom Christiansen, & Jon Orwant.

perl标准版手册以及perldoc.com在线文档http://www.perldoc.com,或者命令行perldoc pagename。
□ perlrun - perl interpreter options
□ perlfaq3 “Why don’t Perl one-liners work on my DOS/Mac/VMS system?”

unix在线文档http://www.bsdi.com/bsdiman/,或者命令行“man pagename”.
□ find
□ wc
□ xargs