perl一行式选项、特殊变量参考手册

perl一行式程序系列文章Perl一行式


本文用来收集Perl一行式中涉及到的一些选项、特殊变量的说明,可以用来做速查手册。

第一次学Perl一行式时,请直接忽略本文内容,并直接从后面的示例部分开始看。本文会在每一个示例中解释出现的选项、变量、函数和语法。

一行式选项

perl一行式语法:

1
2
perl [-0aFlimMnps] -e 'EXPRESSION' ARGUMENTS
perl [-0aFlimMnps] -E 'EXPRESSION' ARGUMENTS

其中-e-E选项用于指定待运行的表达式,它们之间的不同点在于-E会自动启用高版本功能特性,例如可以直接使用say()函数而无需先use 5.010;

可以指定多个-e/-E,但使用多个的时候,注意每个表达式后面的’;’结尾符号,否则语法报错。

-n和-p

这两个选项都表示按照隐含的逻辑直接处理表达式后面的参数代表的文件。如果perl -e命令行中没有这两个选项,则只能自己在-e表达式中编写读取文件、处理数据、输出/删除的代码逻辑。

-n选项使得perl单行命令以类似于如下代码的方式运行:

1
2
3
4
LINE:
while(<>){
...-e expression CODE HERE...
}

由于while中使用的是<>,所以它会从@ARGV中读取参数文件进行处理。

perl -n就像sed -n一样,表示禁止默认的输出。如果想要强制输出,只能在-e表达式中自行指定输出操作,例如print/say/printf。

-p选项使得perl单行命令以类似于如下代码的方式运行:

1
2
3
4
5
6
LINE:
while(<>){
...-e expression CODE HERE...
}continue{
print or die "-p destination: $!\n";
}

perl -p用于强制输出$_,它会覆盖-n选项。

必须要注意的是-n和-p都采用<>来读取文件,而它从文件中读取每一行时会保留每一行的尾随换行符\n

何时使用-p,何时使用-n

-p和-n的逻辑虽然很简单,也如sed的-n和p命令类似,但对于初学perl一行式程序的人来说,仍然很容易迷惑,因为Perl是一门语言,perl一行式也一样可以写成一门简单的语言,这意味着几乎总是有多种一行式的方式实现一个需求。

例如,-p可以被-n + print替代,-n、-p都可以被-e中的while(<>)替代。

但既然perl一行式提供了-n和-p的选项,写perl一行式的时候自然应该追求精简化,让-e表达式的代码逻辑更简单。

根据我个人的总结:

  1. 只要不操作文件,就不需要-n和-p
  2. 某些行不需要输出,或者需要被删除的时候,也就是说不需要输出所有行时,不应该使用-p,因为它默认会输出所有行
    • 换句话说,如果需要输出所有行,就可以考虑使用-p
    • 使用-p的时候,在-e表达式中只需操作$_,例如对$_的赋值、s替换,此时不需要额外的print,但有些操作是可以隐藏$_的,最常见的就是s替换命令
  3. 不使用-p的时候,几乎总是可以使用-n,这时需要在-e表达式中手动print
  4. 如果处理文件的需求实在复杂,那么不要-n和-p,自己在-e中写文件读取的逻辑。但这种情况很少,真的出现这种情况,一般写成Perl脚本更好

-l选项

1
2
-l
-lOCTNUM

选项开启自动行尾处理功能。它有两个过程:

  1. 和-n和-p一起使用的时候,将自动对输入行执行chomp剔除输入行终止符
  2. 将print的输出行分隔符变量$\设置为OCTNUM的八进制数值,OCTNUM的ASCII字符将追加在输出的每一行行尾。如果省略了OCTNUM,则将$\设置为输入行分隔符变量$/的值,通常是换行符

需要注意的是,省略OCTNUM的时候,也就是只有-l的时候,会在处理这个选项的那一刻就完成$\ = $/的赋值,所以对于-ln0e EXPRESSION将进行两段赋值:

1
2
3
4
# 处理-l的时候
$\ = $/;
# 处理-0的时候
$/ = \0;

这使得输出行分隔符取输入行分隔符的值,并在之后修改输入行分隔符。

注意上面的-0选项不能直接放在-l选项后(也就是-l0ne),这会产生歧义,认为0是-l选项的参数值,而不是-0选项。

一般来说,-l选项是用来为print函数追加换行符的,所以-l经常结合-n选项一起使用。例如:

1
$ perl -lne 'print' file.log

-0选项

设置输入行分隔符$/

1
-0[octal/hexadecimal]

-0使得perl读取行时,以\0作为行输入分隔符,也就是对行输入分隔符变量$/进行赋值:$/ = "\0";

如果没有给定任何选项参数,则表示设置为null,即等价于$/ = undef,这表示一次性从文件头部一直读取到文件尾部当作一行。

如果给定了OCTNUM,即\0OCTNUM,则将八进制数值OCTNUM对应的ACSII作为行输入分隔符。

特别地,设置-00表示将$/设置为空"",即$/ = "",这表示按段落读取到$_且压缩连续空行。

-a和-F选项

-a选项打开自动分割模式,只能在-n或-p模式下使用

-a使得-n或-p的while循环中首先对行进行一次隐含的split分割操作,并将分割后的结果放进数组@F中,使其可以成为一个个的字段,并通过$F[n]的方式调用各字段,其中n为字段的索引号,从0开始计算。

分割后的元素全都收集到一个数组@F中,所以第一个字段的内容是$F[0],最后一个字段是$F[-1]$F[$#F]

如果想取多个字段,可以对数组@F进行切片,例如第3个字段和第第5个字段@F[2,4],第3个字段到倒数第二个字段是@F[2..$#F-1]@F[1..~~@F-2]。不要忘记,$_这时表示整行,类似于awk里的$0,而Perl中的$0表示的是程序名称(对于perl一行式,$0的值为-e-E)。

通过-a选项,可以使perl单行程序可以以类似于awk的方式运行,只不过awk的第一个字段是从$1开始的,而-a的第一个字段是从$F[0]开始的。

-a划分字段的分隔符由-F选项指定。如果没有指定-F,则默认以空白符号进行分割(连续空格被认为是单空格)。

可以使用-Fpattern指定字段分隔符,也就是作为split的第一个参数。pattern可以被双斜线//、单引号''或双引号""包围,如果没有给定符号,则默认使用单引号。

1
2
3
4
-F:
-F/:/
-F":"
-F':'

关于-F的pattern,普通的字符直接包围即可,但想要指定空白符号作为分隔符,必须注意官方手册中的一句话:You can't use literal whitespace or NUL characters in the pattern,也就是说不允许直接指定空白符号作为分隔符。

实际上-Fpattern可以替换为split /pattern/;。所以想要指定空白符号作为分隔符,别用-F,而是直接使用split函数:

1
$ perl -alne 'split / /;print $F[1]' file.log

此外,空白符号可以使用\s来替代:

1
$ perl -F'\s+' -alne 'print $F[-1]' file.log

注意:

当Perl一行式命令中使用需要划分字段时(比如使用-a选项),Perl处理速度会急剧下降。Perl总是使用split划分字段,而split总是使用正则表达式进行字段的划分,但正则表达式的划分方式性能较低。

awk根据FS变量划分字段,它可以是正则,也可以是精确的字符,如果awk使用正则划分字段,它的速度也会很慢,但如果不是正则表达式划分字段,那么awk的速度会很快很快,远远快于Perl。(此处所述awk为gawk版本)

因此在处理大量行数据且需要划分字段时请考虑清楚要不要使用Perl,如果可以,应测试awk实现相同功能的效率。

-i选项

1
-i[extension]

这个选项和sed的-i选项功能一样,都是为了提供副本或原地修改。

如果不指定extension,则perl打开一个临时文件的文件句柄作为print的输出对象,处理完成后将此临时文件重命名为原始文件名。所以,原始文件会被删除,且原始文件的写权限不影响-i选项的重命名,只有文件所在目录的写权限才能限制-i创建新文件。

如果指定了extension,则表示拷贝原文件为一个新的文件名,然后原始的文件名作为print的输出对象。例如-i'.bak'表示拷贝原始文件并命名为FILENAME.bak,然后原始的文件名FILENAME是perl的输出目标。也就是说,操作完成后,FILENAME是被修改的,FILENAME.bak是未做修改的原始内容。

如果extension中没有包含*,则字符作为原文件名的后缀,例如-i".bak"。如果extension中包含了*,则每个*都代表原始文件名,这使得可以给文件加前缀、后缀等。例如-i"file-*-1-*.bak"将命名为file-FILENAME-1-FILENAME.bak

-m和-M选项

这两个选项用来导入模块。

1
2
3
4
-mmodule
-Mmodule
-M'module ...'
-[mM]module=arg[,arg]...

-m导入模块时,相当于执行了use module ();,这表示在程序中必须使用完整的名称来引用模块中的属性。例如-m'List::Util'时,在-e表达式中要使用其max函数,需要指定为List::Util::max @arr

-M导入模块时,相当于执行了use module;,这时要使用模块中的属性仍然需要些完整的名称。但-M还支持额外的参数。例如-M'List::Util qw(max sum)'表示导入List::Util模块中的max和sum函数,这时在-e表达式中可以直接使用这两个函数名,而无需写完整的名称List::Util::max @arr

对于-m和-M,还有另一种=的写法,这种写法使得-m和-M没有任何的区别。例如
-m'List::Util=sum,max'-M'List::Util=sum,max'是等价的,它们都表示导入这List::Util模块中的sum和max函数,使得使用这两个函数的时候无需再写完整名称。

-s选项

perl的-s选项允许解析--之后的-xxx=yyy格式的开关选项。

1
$ perl -e 'xxxxx' -- -name=abc -age=23 a.txt b.txt

--之后的参数,它们本该被收集到ARGV中,但如果开启了-s选项,这些参数部分如果是-xxx=yyy格式的,则被解析成perl的变量$xxx并为其赋值yyy,而那些不是-xxx=yyy格式的参数则被收集到ARGV数组中。

例如:

1
2
3
4
5
6
$ perl -se '
print "ARGV: @ARGV\n";
print "NAME & AGE: $name,$age\n";
' -- -name="longshuai" -age=23 abc def
ARGV: abc def
NAME & AGE: longshuai,23

上面传递的参数是-name="longshuai" -age=23 abc def,因为开启了-s选项,所以解析了两个perl变量$name=longshuai $age=23,另外两个abc def则被收集到ARGV数组中。

特殊变量

$\

表示print函数的输出行分隔符。默认为undef,所以print默认输出时两端数据总是连在一起的。

可以指定为换行符\n,这样print输出每段数据都会自带换行符。

1
2
$ perl -e '$\ = "\n";print "haha"'
haha

但必须注意,对于perl一行式程序的-p选项,它通过<>读取数据时会保留每一行的尾随换行符(除非在-e表达式中使用了chomp/chop),这时不应该额外设置$\ = "\n",否则每行后面都会多输出一空行。

$/

表示读取文件时的输入行分隔符,默认为换行符\n。在读取文件的时候,通过该特殊变量可以控制如何分行。

可以设置为多个字符。

如果设置为undef,表示一次性从文件头一直读取到文件尾当作一行。

如果设置为空""\n\n,表示按段落读取。不同的是:

  • 设置为空""时,表示压缩连续空行为单个空行,压缩后的单空行属于当前段落
  • 设置为\n\n则盲目地认为下一行一直属于当前段落,即使是空行,也即是不会压缩连续空行

$.

$.表示当前处理的行的行号。

实际上,它表示的是当前正在被打开的文件句柄的行号计数器。只要文件句柄不显式关闭,行号计数器就不会重置(open的隐式关闭以及reopen都不会重置)。

<>读取ARGV文件时从不关闭文件句柄,所以在一行式perl程序中使用了-n/-p时,多个参数文件的行号是连续下去而不会重置的。

如果确实想要重置<>所读取的每个文件的行号,可以通过eof函数来判断,在到了文件底部的时候就关闭当前处理的文件。

1
2
3
4
5
while(<>){
print "$. $_\n";
} continue {
close(ARGV) if eof #注意,不是eof()
}

例如:

1
$ perl -e 'while(<>){print "$. $_"} continue {close(ARGV) if eof}'

eof和eof()是不同的,前者判断每个文件的文件尾部,后者则判断最后一个文件的尾部(也就是无内容可读了)。所以,下面的表示在最后一个文件的前面插入一行分割线。

1
2
3
4
while(<>){
print "-" x 30,"\n" if eof();
print "$. $_"
}

@F

是”-a”选项将行自动分割后,各字段所保存的数组容器。具体参见-a选项。

$,$"

$,变量指定print/say输出无双引号包围的列表/数组时的元素分隔符,默认值为undef,也就是元素之间紧紧相连。

$"变量指定print/say输出双引号包围时的列表/数组时的元素分隔符,默认值为空格。

1
2
3
4
5
$ perl -le '@arr=qw(Shell Perl Python);print @arr'
ShellPerlPython

$ perl -le '@arr = qw(Shell Perl PHP);print "@arr"'
Shell Perl PHP

指定特殊变量$,$"的值:

1
2
3
4
5
6
7
8
9
10
11
$ perl -le '
@arr=qw(Shell Perl Python);
$,=" ";
print @arr'
Shell Perl Python

$ perl -le '
@arr=qw(Shell Perl Python);
$"=":";
print "@arr"'
Shell:Perl:Python