Perl命令行应用程序详解
Perl命令行参数丰富,通过-e执行单行代码,-n和-p实现隐式逐行循环,-i开启原位编辑,-M简化模块加载,-l自动处理换行,-a配合-F实现自动拆分。-c仅检查语法,-W开启所有警告,-T启用taint模式保障安全。这些参数使脚本精简高效。
Perl——Practical Extraction and Report Language——拥有大量命令行参数,善用它们能让你的脚本精简到极致,甚至一行命令就搞定所有事情。这篇文章就来系统梳理一下那些最常用的命令行参数。

命令行调用
perl [ -sTtuUWX ]
[ -hv ] [ -V[:configvar] ]
[ -cw ] [ -d[t][:debugger] ] [ -D[number/list] ]
[ -pna ] [ -Fpattern ] [ -l[octal] ] [ -0[octal/hexadecimal] ]
[ -Idir ] [ -m[-]module ] [ -M[-]'module...' ] [ -f ]
[ -C [number/list] ]
[ -P ]
[ -S ]
[ -x[dir] ]
[ -i[extension] ]
[ [-e|-E] 'command' ] [ -- ] [ programfile ] [ argument ]...
[开关项描述]
-0
规定记录分隔符。
-0<数字>
(用8进制表示)指定记录分隔符($/变量),默认为换行。
-00
段落模式,即以连续换行为分隔符。
-0777
禁用分隔符,将整个文件作为一个记录。
-a
与 -n 或 -p 搭配,打开自动拆分模式。默认按空白字符拆分,结果存入@F,相当于@F = split ''。可以通过 -F 自定义分隔符。举个例子:
date | perl -ane 'print "$F[0]n"';
-c
只检查 Perl 脚本语法,不执行。每次修改脚本后第一时间用它来捉语法错误。
-d
启动 Perl 调试器。
-D
设置调试标记。例如 -D14 可以观察脚本的执行过程。使用前请确认调试器已正确安装。
-e command
直接在命令行执行 Perl 代码,无需写脚本文件。这是单行命令的基石。
-F pattern
指定拆分输入行时的模式。模式是正则表达式,可以放在斜杠、单引号或双引号中。例如 -F/:+/ 表示按一个或多个冒号拆分。如果同时使用 -a,则自动生效。
-h
打印 Perl 命令行选项列表。
-i extension
启用原位编辑模式。使用 <> 循环遍历文件时,如果需要修改原文件,-i 就派上用场了。如果提供扩展名(如 .bak),则原始文件会被备份为 原文件名.扩展名,然后修改写入原文件。如果不提供扩展名,则直接覆盖。
-I directory
与 -P 配合,告诉 C 预处理程序去哪里查找包含文件。默认路径包括 /usr/include、/usr/lib/perl 和当前目录。
-l digits
启用自动行尾处理。与 -n 或 -p 一起使用时,会自动移除输入行的换行符,并将输出行附加换行符。如果不指定数字,则把 $/ 的当前值赋给 $\。简单理解:-l 同时做了两件事——自动 chomp 输入分隔符,并在 print 时自动加换行。
-m [-] module
-M [-] module
-M [-] module'
-[mM] module =
arg [,arg]..
-m module
在执行 Perl 脚本之前调用 use 方法。其中 -M 相当于 use Module;,-m 相当于 use Module ();(即不导入默认符号)。方括号中的短横线表示将 use 替换为 no。
-n
让 Perl 隐式循环遍历所有文件。相当于 while (<>) { 脚本; }。每一行默认保存在 $_ 中。
-p
类似 -n,但会在循环末尾自动打印 $_。相当于 while (<>) { 脚本; print; }。简单来说,-p 适合完全处理文件,-n 适合选择性输出。
-P
在 Perl 编译之前先通过 C 预处理程序处理脚本。
-s
启用自定义开关解析。脚本名之后、文件名参数之前的开关会被解析为对应的标量变量(如 -abc 会生成 $abc 并赋值为 1),并从 @ARGV 中删除。
-S
如果脚本首行不是 #!/usr/bin/perl,则让 Perl 在 PATH 环境变量中搜索脚本。
-T
强制打开“污染”检查(taint mode),常用于测试 setuid/setgid 程序,也推荐用于 CGI 脚本。
-u
编译后产生核心转储(仅 Unix 系统)。
-U
允许 Perl 执行不安全操作,例如超级用户断开目录链接。
-v
打印 Perl 版本信息。
-V
打印重要配置项和 @INC 数组的汇总。
-V:NAME
打印指定配置变量 NAME 的值。
-w
打印警告信息,包括保留字误用、文件句柄错误等。
-W
启用所有警告,即使局部代码中用 no warnings 关闭了也无效。
-x directory
忽略 #!/usr/bin/perl 行之前的任何文本。如果指定了目录名,Perl 会在执行前自动切换到该目录。
-X
关闭所有警告。
另外提一句:-p 和 -n 都隐式地把每一行保存在 $_ 中,区别仅在于 -p 会自动打印 $_。
安全网参数
有三个参数堪称“安全网”,尤其当你用 Perl 玩一些“聪明”或疯狂的想法时,它们能让错误提前现形。经验丰富的 Perl 程序员几乎离不开它们。
首先是 -c。编译但不执行,只检查语法错误。每次修改脚本后先跑一遍 perl -c program.pl,确保编译通过。在输入少量代码时就检查,比后面面对几百行代码再 debug 轻松得多。
第二个是 -W,它开启所有警告,能揪出潜在的 bug。不过 Perl 5.6.0 之后推荐在脚本内使用 use warnings;,因为更灵活。
第三个是 -T,进入 taint 模式。Perl 会怀疑所有来自外部的数据(命令行、文件、CGI 输入),这些数据被“污染”后不能直接用于 system 调用、open 等危险操作。要使用它们,必须通过正则表达式“去污染”。taint 模式本身内容很多,这里不展开,但如果你在写 CGI 等需要接收用户输入的程序,强烈推荐开启 taint 模式。
另外 -d 参数进入调试器模式,详细内容可参考 perldoc perldebug 或 Richard Foley 的 Perl Debugger Pocket Reference。
使用模块
下面几个参数让命令行上的 Perl 程序更简洁。-e 可以直接执行代码,比如一行搞定“Hello World”:
$ perl -e 'print "Hello Worldn"'
多个 -e 可以按顺序执行:
$ perl -e 'print "Hello ";' -e 'print "Worldn"'
引入模块时,-M 比在 -e 中用 use 更方便:
$ perl -MLWP::Simple -e 'print head "http://www.example.com"'
-M 等同于 use Module;,包含默认导入。如果不想要默认导入,就用 -m,它相当于 use Module ();。例如下面的例子因为 head 函数是默认导入,使用 -m 时不会被导入,所以什么也输出:
$ perl -mLWP::Simple -e 'print head "http://www.example.com"'
-m 和 -M 还支持在 = 后附加参数,比如引入 :standard 标签:
$ perl -MCGI=:standard -e 'print header'
要引入多个参数,可以用引号加逗号分隔:
$ perl -MCGI='header,start_html' -e 'print header,start_html'
隐式循环
-n 和 -p 为逐行处理文件提供了循环框架。
$ perl -n -e 'some code' file1
相当于:
LINE:
while (<>) {
# your code goes here
}
每一行自动存入 $_。下面的例子输出行号和内容:
$ perl -n -e 'print "$. - $_"' file
而 -p 会自动调用 print,所以同样的效果可以写成:
$ perl -p -e '$_ = "$. - $_"' filename
注意隐式循环中有一个 LINE: 标签,可以用 next LINE 直接跳到下一个输入记录,这在多层嵌套时非常有用:
$ perl -n -e 'next LINE unless /pattern/; print $_'
当然也可以简化成 print unless /pattern/。如果需要在循环前后做额外处理,可以使用 BEGIN 或 END 块。下面这个例子统计文本文件中的总字数:
$ perl -ne 'END { print $t } @w = /(w+)/g; $t += @w' file.txt
如果再加上 -a,自动拆分模式会把每行按空白分割成数组 @F,上面的代码可以更简洁:
$ perl -ane 'END {print $x} $x += @F' file.txt
通过 -F 可以自定义拆分规则。例如按非单词字符拆分:
$ perl -F'W' -ane 'END {print $x} $x += @F' file.txt
一个经典的例子:分析 Unix 密码文件(/etc/passwd),按冒号拆分,统计每个 shell 路径被多少用户使用:
$ perl -F':' -ane 'chomp($F[6]); $s{$F[6]}++; END{print"$_:$s{$_}n" for keys %s}' /etc/passwd
输出类似:
/usr/sbin/nologin : 3
/bin/sh : 18
/bin/sync : 1
/bin/bash : 2
/bin/false : 9
数据分隔符
之前提到过 $/(输入记录分隔符)和 $\(输出记录分隔符)。$/ 默认是换行符,所以一次读一行。$\ 默认为空,因此 print 时需要手动加 \n。在命令行中,-0(零)对应 $/,-l(字母 L)对应 $\。-0 后面可以跟八进制或十六进制数值,例如 -00 进入段落模式(连续换行分隔),-0777 进入 slurp 模式(一次读入整个文件)。-l 单独使用有两个效果:一是自动 chomp 输入分隔符,二是把 $/ 的值赋给 $\,这样 print 时自动加换行。个人常常使用 -l 来省去末尾的 \n:
$ perl -le 'print "Hello World"'
原位编辑
配合 -i 参数,可以原地修改文件,而不需要 I/O 重定向。例如将所有 PHP 替换为 Perl(整词匹配):
$ perl -i -pe 's/bPHPb/Perl/g' file.txt
如果想保留备份,就加个扩展名:
$ perl -i.bak -pe 's/bPHPb/Perl/g' file.txt
处理后的结果写入 file.txt,原始文件则被重命名为 file.txt.bak。
打开Perl的taint模式
如果程序从外部接收数据,并且这些数据会影响到程序之外的东西(比如系统调用),那么就应该用 -T 开启 taint 模式。Perl 会检查数据是否被“污染”,如果未经处理就用于危险操作,会抛出一个致命错误。去污染的唯一方法是用正则表达式匹配并提取你想要的部分,从而明确描述数据的格式。例如,期望收到一个单词字符:
if ($data =~ /^(w+)$/) {
$data = $1;
} else {
die "Error: tainted data found: $datan";
}
Perl命令行调试
缺省的 Perl 调试器就是解释器本身,启动方式很简单:perl -d test.pl。进入交互界面后,常用命令如下(所有命令在调试提示符下输入,大小写有关):
h:显示帮助。|h:分页显示帮助。p 表达式:打印变量或表达式的值(不显示复杂结构)。x 表达式:打印变量值,对复杂数据以可读形式展示。V 包名 变量列表:显示指定包内的变量(包名默认 main)。X 变量列表:显示当前包内的变量。T:调用栈回退一级。s:单步执行,进入子函数(step into)。n:单步执行,越过子函数(step over)。c 行号/函数名:执行到指定行或函数。l:显示未执行的一屏代码。l min-max:显示指定行范围。/模式:向前查找。?模式:向后查找。b 行号 [条件]:设置断点。b 函数名 [条件]:函数断点。d 行号:删除断点。D:删除所有断点。a 行号 命令:添加附加操作(执行该行前先执行命令)。W 表达式:添加监视项。!number:重复执行之前第 number 次调试命令。!!命令:执行 shell 命令而不退出调试器。q:退出。
更多细节可参考 perldoc perldebug。
更多信息
Perl 命令行参数远不止这些,本文只列出了最实用的一部分。完整列表请参考 perlrun 文档。
命令行的运用
Perl 在命令行下完全可以替代 sed、awk 等工具。有经验的程序员不应回避那些看似“丑陋”但高效的快速解决方法。
Perl单行命令示例
先看参数如何被接收:
perl -MData::Dumper -e 'print Dumper @ARGV' a b -w
$VAR1 = [
'a',
'b',
'-w'
];
-e 后的内容被当作脚本执行。-M 导入模块。注意单引号比双引号更安全,因为 shell 不会转义里面的变量。
perl -e 'print "$$n"'
5719
上例打印当前进程 PID,单引号保护了 $$ 不被 shell 解释。
原地替换:
perl -pi -e 's/aaa/bbb/' filename
类似 grep 过滤:
perl -ne 'print if /^int/' filename
加行号打印:
perl -n -e 'print "$. - $_"' filename
使用 -p 简化:
perl -pe '$_ = "$. $_"' filename
利用 -a 和 -F 进行字段处理:
perl -lane 'print $F[0] + $F[-2]'
打印从 START 到 END 之间的行:
perl -ne 'print if /^START$/ .. /^END$/'
打印指定行号范围:
perl -ne 'print if $. >= 15; exit if $. >= 17;'
原地替换并备份:
perl -p -i.bak -e 's/bfoob/bar/g' *.c
反向每一行:
perl -ne 'print scalar reverse $_' test
替换
所有 C 程序中的 foo 替换为 bar,旧文件备份为 .bak:
perl -p -i.bak -e 's/bfoob/bar/g' *.c
将每个文件中的数字加一:
perl -i.bak -pe 's/(d+)/ 1 + $1 /ge' file1 file2 ....
将 \r\n 替换为 \n(类似 dos2unix):
perl -p -i -e 's/rn/n/g' file
反向(类似 unix2dos):
perl -pie 's/n/rn/g' file
取出文件的一部分
显示字段 0-4 和字段 6(默认空格分隔):
perl -lane 'print "@F[0..4] $F[6]"' file
如果分隔符是冒号(如 /etc/passwd):
perl -F: -lane 'print "@F[0..4]n"' /etc/passwd
显示 START 和 END 之间的部分:
perl -ne 'print if /^START$/ .. /^END$/' file
不显示中间部分:
perl -ne 'print unless /^START$/ .. /^END$/' file
显示前 50 行:
perl -pe 'exit if $. > 50' file
跳过前 10 行:
perl -ne 'print unless 1 .. 10' file
显示第 15 到 17 行:
perl -ne 'print if 15 .. 17' file
每行取前 80 个字符:
perl -lne 'print substr($_, 0, 80) = ""' file
每行丢弃前 10 个字符:
perl -lne 'print substr($_, 10) = ""' file
搜索
查找包含 comment 的行:
perl -ne 'print if /comment/' duptext
查找不含 comment 的行:
perl -ne 'print unless /comment/' duptext
查找包含 comment 或 apple 的行:
perl -ne 'print if /comment/ || /apple/' duptext
计算
计算第 5 字段和倒数第 2 字段之和:
perl -lane 'print $F[4] + $F[-2]'
排序和反转
文件按行排序:
perl -e 'print sort <>' file
文件按段落排序:
perl -00 -e 'print sort <>' file
合并多个文件后整体排序:
perl -0777 -e 'print sort <>' file1 file2
文件按行反转:
perl -e 'print reverse <>' file1
数值计算
十进制转十六进制:
perl -ne 'printf "%xn",$_'
十进制转八进制:
perl -ne 'printf "%on",$_'
十六进制转十进制:
perl -ne 'print hex($_)."n"'
八进制转十进制:
perl -ne 'print oct($_)."n"'
简易计算器:
perl -ne 'print eval($_)."n"'
批量重命名文件
利用 File::Find 模块遍历目录并改名。例如将 帝王之死001.mp3 重命名为 01.mp3:
$ ls
帝王之死001.mp3 帝王之死006.mp3
$ perl -MFile::Find -e 'find sub{ rename $_,substr($1,1,2).".mp3" if /(d+).mp3$/;}, "." '
$ ls
01.mp3 06.mp3
更复杂的例子,去掉中文引号、替换分隔符等,这里不再一一展开,核心就是 find 结合正则表达式。
在命令行下查看当前目录下的目录
使用内置 File::Find 列出所有子目录(包括深层):
perl -MFile::Find -E 'find sub{ print "$_," if(-d && length >1) }, $File::Find::prune = 1, "." '
使用 File::Find::Rule 更直观,只列一级目录:
perl -MFile::Find::Rule -E 'say for grep $_ ne ".", File::Find::Rule->maxdepth(1)->directory->in(".");'
对目录下的文件进行大小写修改
将大写目录名改为小写:
$ ls
CD1 CD2 CD3 CD4 CD5 CD6
$ perl -MFile::Find -e 'find sub{ rename $_,lc($1) if /(w+d$)/;}, "." '
$ ls
cd1 cd2 cd3 cd4 cd5 cd6
统计相关的tcp连接情况
查看 IP 尾数为 9、端口 80 的所有连接:
netstat -naut|grep 9:80|perl -F's+' -alne '$F[4]=~/(.*):d+/;$F[4]=$1;$s{$F[4]}++;' -e 'END { foreach (sort {$a<=>$b} keys %s){print "$_ : $s{$_}"} }'
按连接数降序排列,显示前 20 个 IP:
netstat -naut|grep 9:80|perl -F's+' -alne '$F[4]=~/(.*):d+/;$F[4]=$1;$s{$F[4]}++;' -e 'END {foreach (sort {$s{$b}<=>$s{$a}} keys %s){print "$_ : $s{$_}"} }' |head -20
统计连接状态:
netstat -naut|grep 9:80|perl -F's+' -alne '$s{$F[5]}++;' -e 'END { foreach (keys %s){chomp($_);print "$_ : $s{$_}"} }'
统计文本文件行数,有多种写法,但实测 wc -l 效率最高。
perl -le 'open FILE, "file_name"; @_=
perl -e 'print scalar(()=<>),"n"' file_name
perl -wE'say~~(()=<>)' file_name
perl -e'print scalar(()=<>)' file_name
perl -lne 'END{print $.}' file_name
perl -le 'print $==()=<>' file_name
perl -ne 'print $. if eof' file_name
其他
启动交互式 Perl 调试器:
perl -de 1
查看 @INC 路径:
perl -le 'print for @INC'
perl 生成随机密码
核心是利用 rand 函数。生成 25 到 75 之间的随机数:
$random = int( rand(51)) + 25;
print "$randomn";
从数组中随机取一个元素:
$elt = $array[ rand @array ];
$elt = $array[ int( rand(0+@array) ) ];
生成 8 位包含特殊字符的随机密码:
@chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ % ^ & *) );
$password = join("", @chars[ map { rand @chars } ( 1 .. 8 ) ]);
更灵活的方式:
use strict;
use warnings;
sub random_pwd {
my $length = shift;
my @chars = (0 .. 9, 'a' .. 'z', 'A' .. 'Z');
return join '', @chars[ map rand @chars, 0 .. $length ];
}
print random_pwd(42);
一行命令生成 42 位密码:
perl -le "print map { ('a'..'z', 'A'..'Z', '0'..'9')[rand 62] } 1..42"
也可以调用 String::Random 模块:
use strict;
use warnings;
use String::Random 'random_regex';
print random_regex('[a-zA-Z]{42}'), "n";
相关问题:Perl one-liner 中处理单引号
在命令行中,单引号嵌套容易出问题。假设你想输出带单引号的字符串:
echo "a,b" | perl -F',' -lane 'print $F[0];'
'a'
如果直接试图包裹单引号,可能会失败。解决办法:1)使用双引号包裹外层,内部用转义;2)使用 ASCII 码 \047。例如:
echo "a,b" | perl -F',' -lane 'print "'''$F[0]'''";'
'a'
或者:
echo "a,b" | perl -F',' -lane 'print "$F[0]\047";'
a'
这些技巧在实际的单行命令中很实用,值得收藏。


































