Perl one line command 中文简介
2018-03-21 18:25:20 阿炯

本文源自:https://highdb.com/tag/perl/

文章参考: PERL ONE-LINERS Copyrught @ 2014 by Peteris Krumins ISBN-10: 1-59327-520-X

本文可以认为是"Perl单行应用详解"此文的延伸。

Perl one line command -- 介绍

Perl 命令行程序既轻巧又便捷,它特指单行代码的 Perl 程序,处理一些事情会特别方便,比如:更改文本行的空白符,行数统计,转换文本,删除和打印指定行以及日志分析等。

熟悉命令行操作后可以节省我们大量的时间成本, 当然了解 Perl 的基本语法和一些特殊符是学习 perl 命令行的基础,Perl 在 5.8 和 5.10 版本对命令行的支持都很好.

先来介绍下常用的参数:
   -a 参数在和 -n 或 -p 参数使用时, 启用自动分割模式, 将结果保存到数组@F(等同 @F = split $_ )
   -e 参数允许 Perl 代码以命令行的方式执行.
   -n 参数相当于在代码外围增加了while(<>)处理.
   -p 参数等同-n参数, 不过会打印出行内容.
   -i 参数保证文件 file 在编辑之前被替换掉, 如果我们提供了扩展名, 则会对文件 file 做一个以扩展名结尾的备份文件.
   -M 参数保证在执行程序之前,加载指定的模块.
   -l 参数自动chomp每行内容(去掉每行结尾的换行符)同时在打印输出的时候又加上换行符到结尾.

我们以一些常用的示例开始说明:
perl -pi -e 's/from/to/g' file

该命令会替换文件file中所有的from为to, 如果要在windows中执行,将上述的 '换位" 即可.

因此很容易理解以下示例,先生成 file.bak 备份, 再进行替换操作:
perl -pi.bak -e 's/from/tp/g' file

如果有多个文件, 则依次进行处理:
perl -pi.bak -e 's/from/to/g' file1 file2 file3

在这里, 我们可以使用正则来只处理匹配到的行:
perl -pi.bak -e 's/from/tp/g if /there/' file

将上面的命令行, 还原为 Perl 程序, 类似如下:
while(<>) {
    if($_ =~ /there/) {
        $_ =~ s/from/to/g;
    }
    print $_;
}

这里的正则可以是任何表达式, 比如我们只处理包含数字的行, 可以使用 \d 匹配数字:
perl -pi -e 's/from/to/g if /\d/' file

同样我们也可以统计文本中相同行的信息, 打印超过一次的行:
perl -ne 'print if $a{$_}++' file

这条命令使用了哈希 %a, 统计了一行内容出现的次数, 哈希的 key 为行的内容, 值为出现的次数; 在处理一行记录时, 如果 $a{$} 为 0, 表示还没有处理过该行内容, 则忽略打印, 同时初始化 $a{$} 赋值为 1; 如果 $a{$_} 大于 0, 则表示已经处理过该行, 这时满足 if 条件, 则打印出该行内容.

$_ 表示当前行的内容.

也可以使用 $. 打印行号, $. 变量维护着当前行号的信息, 只需要将其和 $_ 一起打印即可:
perl -ne 'print "$. $_"' file
perl -pe '$_ = "$. $_"' file

上面两个示例等效, 因为 -p 等同 -n, 同时也进行打印操作.结合上述统计文件相同行的示例, 只打印超过一次的行及行号:
perl -ne 'print "$. $_" if $a{$_}++'

再来处理空白行, 如下示例:
perl -lne 'print if length' file

如果不为空行则打印, 上述等同于 perl -ne 'print unless /^$/' file, 不过和下面示例有点不同:
perl -ne 'print if /\S/'

\S正则匹配非空白字符(空格, 制表符, 新行, 回车等), 索引上述的两个示例并不会过滤制表符类的空行.

另一个例子我们采用 List::Util 模块( http://www.cpan.org )来打印每行中最大的数值,List::Util 是 Perl 的内置模块, 不需要额外的安装,以下示例打印每行中最大的数值:
perl -MList::Util=max -alne 'print max @F' file

-M 导入了 List::Util 模块, =max 导入了 List::Util 模块中的 max 方法, -a 开启了分割模式, 将结果存到 @F 数组中, -l 确保每次打印产生换行.

下面是一个随机生成8位字母组成的口令信息:
perl -le 'print map{ ("a".."z")[rand 26] } 1..8'

"a".."z" 生成字母从 a 到 z 的字母列表, 然后随机的选择 8 次.

我们可能也想知道一个 ip 地址对应的十进制整数:
perl -le 'print unpack("N", 127.0.0.1)'

unpack 对应 pack 函数, N 表示32位网络地址形式的无符号整形, 详见 perldoc -f pack.

如果要进行计算该怎么做? -n 已经表示了 while(<>), 笨一点的方法可以将while放到代码里面, 比如以下:
perl -e '$sum = 0; while(<>){ @f = split; $sum += $f[0]; } print $sum'

但是结合 -a 和 -n 选项则很容易处理:
perl -lane '$sum += $F[0]; END{print $sum}'

END确保在程序即将结束时, 打印出总和.

再来看看统计iptables中通过iptables 规则的包的总量, 第一列显示了每条规则通过的包数, 我们只需要进行统计计算即可:
iptables -nvxL | perl -lane '$pkt += $F[0]; END{print $pkt}'

最后介绍一点perldoc相关的信息, perldoc perlrun命令会显示如何执行 Perl 及命令行参数使用相关的文档信息,这在我们忘记一些参数的时候非常有用;perldoc perlvar显示所有变量相关的文档信息,一些记不住的特殊符总在这里能找到;perldoc perlop 显示了所有操作符相关的信息,应有尽有;perldoc perlfunc 显示所有函数的文档信息, 可以算得上函数大全了。


Perl one line command -- 空白与数字

一. 空白处理

本节说明 Perl 命令行对空白(空行, 制表符)的一些常见处理, 同样以一些示例来说明。

1. 多倍行距
使用特殊符 $\ 来完成多倍行距, $\ 特殊符相当于在每个 input 行后面额外增加了指定的 $\ 变量,如果要将行距扩充两倍,可以如下操作:
perl -pe '$\ = "\n"' file

在每行后面再增加一个换行符, 转换为如下代码:
while(<>) {
   $\ = "\n";
   print $_ or die "-p failed: $!\n";
}

也可以使用 BEGIN 只对 $\ 做一次赋值:
perl -pe 'BEGIN{ $\ = "\n" }' file

上面的示例等效于:
perl -pe '$_ .= "\n"' file
perl -pe 's/$/\n/' file     # 每行结尾改为换行符
perl -nE 'say' file         # Perl 5.10 版的新特性, -E 为开启 Perl 5.10 版特性, say 类似 print, 但增加了换行操作.

如果是多倍行距, 可以使用 “\n”xm(换行符重复m次), 如下3倍行距:
perl -pe '$\ = "\n"x3' file

2. 空行处理
perl -pe '$_ .= "\n" unless /^$/' file

unless 等同 if not, ^$表示匹配空行(^表示开始, $表示结束), 如果空行中存在制表符的话, ^$ 并不会匹配上, 这个示例等同下面:
perl -pe 'print if length' file

如果不为空行(空行length应该为0)则打印, 如果存在制表符的行, print 还是会打印出来, 可以使用下面的代码:
perl -pe 'print if /\S/' file

\S正则匹配非空白字符(空格, 制表符, 新行,回车等),索引上述的两个示例并不会过滤制表符类的空行,也可以在每行之前增加一个空行:
perl -pe 's/^/\n/' file

我们将 -p 参数改为 -n 参数即可移除所有的空行, 因为 -p 本身就会打印出当前行内容:
perl -ne '$_ .= "\n" unless /^$/' file
perl -ne 'print if /\S/' file

3. 多个空白行改为一个空白行或指定行距
如下所示:
perl -00 -pe '' file
perl -00pe0 file

使用 perldoc perlrun 可以搜索到 -00, -0777所表示的意思, 00表示按照段落读取(slurp files in paragraph mode),替代了原先的按行读取, 0777表示整个文本读取, -00指定了按照段落读取内容,再输出来,最后就实现了多个空行改为了一个空行. 这里的 -e ” 和 e0 表示什么都不做.同样的, 使用下面的代码可以将一个行距扩充到多个:
perl -00 -pe '$_ .= "\n"x3' file   # 每段落增加了3个换行符

4. 单词之间的距离
perl -pe 's/ /  /g' file

将一个空格扩充到2个空格. 也可以移除单词之间的空格:
perl -pe 's/ +//g' file

+(前面有个空格) 表示匹配一个或多个空格.如果有制表符,换行符等, 需要用 \s+ 来匹配:
perl -pe 's/\s+//g' file

也可以在每个字符之间插入一个空格:
perl -lpe 's// /g' file

二. 数字处理

本节说明 Perl 命令行对数字的处理。

1. 行号
使用 $. 特殊符表示行号, $_ 表示当前行内容:
perl -pe '$_ = "$. $_"' file
perl -ne 'print "$. $_"' file

排除空行的行号:
perl -pe '$_ = ++$x." $_" if /./' file   #打印空行, 但不显示行号
perl -pe '$_ = ++$x." $_" if /\S/' file  #打印空行, 但不显示行号
perl -ne 'print ++$x." $_" if /./' file  #不打印空行
perl -ne 'print ++$x." $_" if /\S/'file  #不打印空行

记录所有行号, 但是不打印空行:
perl -pe '$_ = "$. $_" if /./' file
perl -pe '$_ = "$. $_" if /\S/' file

只生成匹配规则的行号, 但是也打印没有匹配的行:
# perl -pe '$_ = ++$x." $_" if /there/' file
1 how there are
list file
2 there are

只生成匹配规则的行号, 但是不打印没有匹配的行:
# perl -ne 'print  ++$x." $_" if /there/' file
1 how there are
2 there are

生成所有行的行号, 但是只打印匹配规则的行:
# perl -pe '$_ = "$.  $_" if /there/' file
1  how there are
list file
3  there are

2. 输出格式

# perl -ne 'printf "%-5d %s", $., $_' file
1     how there are
2     list file
3     there are

# perl -ne 'printf "%5d %s", $., $_' file
    1 how there are
    2 list file
    3 there are

# perl -ne 'printf "%05d %s", $., $_' file
00001 how there are
00002 list file
00003 there are

使用 printf 函数格式化输出。

3. 打印文本的总行数(类似 wc -l)
# perl -lne 'END{ print $. }' file
3

下面示例等同上面的代码:
perl -le 'print $n = () = <>' file
perl -le 'print $n = (() = <>)' file
perl -le 'print scalar(@fo = <>)' file

这里没有使用 -p 或 -n, <> 表示 file 的文件句柄(所有内容), () = <> 表示将内容放到列表环境中(每个元素为一行内容), 再将 () = <> 复制给 $n, 这时候为标量上下文, $n 的值为列表元素的数量, 所以最后打印出文本的行数. scalar(@fo = <>) 同理.下面的则稍有点不同:
perl -nle ' }{ print $.' file

这个表达式看起来很奇怪, 这里用到了 -n (等同在代码周围增加了while(<>){ }) 参数, 上面的代码等同以下:
while(<>) {
} {
  print $.
}

所以也打印出了最后一行的行号。

4. 统计非空行
perl -le 'print scalar(grep { /./ } <>)' file
perl -le 'print ~~(grep { /./ } <>)' file
perl -le 'print ~~grep { /./ } <>' file

上述三条命令等效, ~~ 即表示标量环境,

5. 统计空行
perl -lne '$x++ if /^$/; END{print $x+0}' file

$x+0 避免未匹配到而引起的未初始化错误, 如果要忽略制表符等, 正则里面应该是 \S. 也可以使用 grep 打印空行数:
perl -le 'print scalar(grep {/^$/}<>)' file
perl -le 'print ~~grep{/^$/}<>' file

6. 统计匹配规则的行数(grep -c)
perl -lne '$x++ if /there/; END{print $x+0}' file

7. 单词计数
文本所有单词计数:
# perl -pe 's/(\w+)/++$i.".$1"/ge' file
1.how 2.there 3.are
4.list 5.file
6.there 7.are

s///中的e选项可以使得替换之前先执行代码, (\w)为匹配并捕获单词, 捕获后放到 $1 变量中, $i 则在每次匹配之前自增。

1.下面为计数每行的单词:
# perl -pe '$i = 0; s/(\w+)/++$i.".$1"/ge' file
1.how 2.there 3.are
1.list 2.file
1.there 2.are

每次循环初始化 $i 的值即可,下面为替换每个单词为其计数的值:
# perl -pe 's/(\w+)/++$i/ge' file
1 2 3
4 5
6 7


Perl one line command -- 计算

本章使用 Perl 命令行进行一些计算方面的示例说明, 比如查找一行中最大/最小的元素, 统计, 移动和替换单词以及计算日期等. 这章里会用到 -a, -M, -F等命令行参数, 也会讲解一些特殊符及数据结构方面的信息.

1. 检查素数
perl -lne '(1x$_) !~ /^1?$|^(11+?)\1+$/ && print "$_ is prime number"' file

先来看看素数的定义: 一个大于1的自然数,除了1和它本身外,不能整除以其他自然数的数称为素数, 否则是合数. 命令行首先将数字转换成一元数据(比如 4 表示为 1111, 5 表示为 11111, 等等), 再用 !~ 排除匹配的正则表达式里的两个条件, 如果都没有匹配, 则该数是素数; 再来看看正则表达式里面的内容, 首先 ^1?$ 表示 0 或 1, 满足一个大于 1 的自然数, ^(11+?)\1+$ 决定了是否有2个或多个 11… 组成为该数, 如果是表示该数可以整除其他自然数. 举例如下, 5 的一元数据表示为 11111, (11+?) 首先匹配 11, 正则表达式成为 ^11(11)+$, 这里的 + 表示一个或多个 11, 但对于5来讲, 不会匹配上; 下面是 (11+?)匹配 111, 正则表达式成为 ^111(111)+$, 同样不会匹配 5, 综上 1×5 满足了不匹配两个正则的条件, 所以它是素数.

2. 计算一行中各列数的和
echo "1 5 7" | perl -MList::Util=sum -alne 'print sum @F'

同前面提到的 -M 参数的示例, 这里使用 List::Util 模块的 sum 方法, 开始 -a 参数, 各列的数值被分割切保存到 数组 @F 中, 再通过 sum 方法计算出该行中所有列之和. 上述命令行的结果为 13. -a 分割默认使用空格, 可以通过 -F 参数指定分隔符, 比如以下示例:
echo "1:5:7" | perl -F/:/ -MList::Util=sum -alne 'print sum @F'
echo "1:5:7" | perl -F: -MList::Util=sum -alne 'print sum @F'

同样的, 如果我们需要计算所有行数的所有列之和, 将每行的数组 @F 保存到一个大数组里, 或者使用上下文环境累加每行的和, 再使用 END 即可满足条件:
[root@cz scripts]# cat file
1:5:7
2:4:6

[root@cz scripts]# perl -F: -MList::Util=sum -alne 'push @s, @F; END{print sum @s}' file
25

[root@cz scripts]# perl -F: -MList::Util=sum -alne '$s += sum @F; END{print $s}' file
25

3. 打乱行中的列项
先来看下面的示例:
[root@cz scripts]# echo a b c d | perl -MList::Util=shuffle -alne 'print shuffle @F'
cabd

List::Util 模块的 shuffle 方法以随机顺序返回 @F 数组的元素列表, 我们使用 $, 特殊符来指定数组元素之间的分隔符, 如下所示:

[root@cz scripts]# echo a b c d | perl -MList::Util=shuffle -alne '$, = ":" ;print shuffle @F'
b:c:d:a

也可以用 join 函数替换 $, :
[root@cz scripts]# echo a b c d | perl -MList::Util=shuffle -alne 'print join ":", shuffle @F'
b:d:c:a

也可以将 shuffle @F 放到匿名函数中打印出来, 我们使用 @{[shuffle @F]}, [shuffle @F] 创建了一个匿名的数组引用, @{}则将它反解析出来:
[root@cz scripts]# echo a b c d | perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"'
d b c a

4. 找到最小/最大的数值
同样我们也可以调用 List::Util 模块的 min, max 方法得到一行中最小/最大的数值, 比如:

[root@cz scripts]# echo 5 7 -1 | perl -MList::Util=min -alne 'print min @F'
-1

也可以找到文本中最小/最大的数值:

[root@cz scripts]# cat file
-9 2 7
-11 -90 0
4 8 19

[root@cz scripts]# perl -MList::Util=min -alne 'push @M, @F; END{ print min @M}' file
-90
[root@cz scripts]# perl -MList::Util=min -alne '$min = min($min || (), @F); END{ print $min}' file
-90

在 Perl 5.10 及之后的版本中, // 操作符类似逻辑操作符 ||, 只不过它会额外判断左边的是否已经定义过; 以 $min // () 为例说明, 如果 $min 已经定义过, 则返回 $min, 否则返回空列表(), 用 perldoc perlop看看手册页关于 // 的解释: "$a // $b" is similar to "defined($a) || $b" (except that it returns the value of $a rather than the value of "defined($a)") and is exactly equivalent to "defined($a) ? $a : $b"

所以下面的示例等效于上面的:
[root@cz scripts]# perl -MList::Util=min -alne '$min = min($min // (), @F); END{ print $min}' file
-90

同理, 我们可以使用 max 方法得到最大的数值.

5. 替换每列值为其绝对值
可以使用 abs 函数得到数值的绝对值,再通过 map 进行映射替换, 如下:
[root@cz scripts]# perl -anle '$, = " "; print map { abs } @F' file
9 2 7
11 90 0
4 8 19

[root@cz scripts]# perl -anle 'print "@{[map{ abs } @F]}"' file
9 2 7
11 90 0
4 8 19

同上述的示例一样, [map{ abs } @F] 构成匿名的数组引用, 再用 @{}反解析出来.

6. 统计行信息
使用上下文环境可以直接打印每行中的列数:
perl -alne 'print scalar @F' file

也可以将行内容追加到列数之后:
perl -alne 'print scalar @F . " $_"' file

通过 END 打印出文本中所有列的信息:
perl -alne '$s += @F; END{ print $s }' file

打印匹配行的所有列信息:
perl -alne '$s += /there/ for @F; END{ print $s }' file

perl -alne '$s += grep /there/, @F; END{ print $s }' file

grep 返回满足正则匹配的元素列表, 不过在标量环境中返回列表的数量,下面的示例打印文本匹配正则的行数:
perl -lne '/there/ && $s++; END{ print $s || 0 }' file

7. 打印 PI 和 e
[root@cz scripts]# perl -Mbignum=bpi -le 'print bpi(20)'
3.1415926535897932385

[root@cz scripts]# perl -Mbignum=PI -le 'print PI'
3.141592653589793238462643383279502884197

bignum 模块提供 bpi 和 PI 两个方法打印 PI 值, bpi 输出精度为 n -- 1, PI 输出精度为 39.

[root@cz scripts]# perl -Mbignum=bexp -le 'print bexp(2,31)'
7.389056098930650227230427460575

bexp(2,31) 等效于 e^2, 再输出 31 -- 1 = 30 精度的浮点数.

8. 时间
打印 Unix 时间戳, time 函数返回从格林尼治时间(1970 01-01 00:00:00 UTC)到当前时间的秒数:
[root@cz scripts]# perl -le 'print time'
1425286022

可读格式获取格林尼治时间, gmtime 返回GMT时区信息:
[root@z6 scripts]# perl -le 'print scalar gmtime'
Mon Mar  2 08:49:26 2015

gmtime 和 localtime 都返回含有 9 个元素的列表:
($second, [0]
 $minute, [1]
 $hour, [2]
 $month_day, [3]
 $month, [4]
 $year, [5]
 $week_day, [6]
 $year_day, [7]
 $is_daylight_saving [8]
)

使用数组切片就可以打印出我们需要的信息, 比如打印 H:M:S

[root@cz scripts]# perl -le 'print join ":", (localtime)[2,1,0]'
16:56:3

打印昨天的时间:
perl -MPOSIX -le '@now = localtime; $now[3] -= 1; print scalar localtime mktime @now'

mktime @now 将9个元素的列表转换为纪元时间格式(epoch time, 即时间戳), 详见 perldoc POSIX, 再用 localtime重构日期格式, 最后使用 scalar 输出, 等同 print scalar localtime(mktime @now) 或 print ~~ localtime(mktime @now).

同理可以得到, 14个月, 9天07秒之前的时间:
perl -MPOSIX -le '@now = localtime; $now[0] -= 7; $now[3] -= 9; $now[4] -= 14; print scalar localtime mktime @now'

9. 计算阶乘
perl -le '$f = 1; $f *= $_ for 1 .. 5; print $f'

也可以使用 Math::BigInt 模块的 bfac 函数:
perl -MMath::BigInt -le 'print Math::BigInt->new(5)->bfac()'

perl -MMath::BigInt -le 'print Math::BigInt->bfac(5)'

10. 计算最大公约数和最小公倍数
先用辗转相除法计算两个数的最大公约数:
perl -le '$n = 20; $m = 35; ($m, $n) = ($n, $m%$n) while $n; print $m'

按照欧几里得算法的定理: gcd(a,b) = gcd(b,a mod b) (a>b 且a mod b 不为0), 上面的命令行在 $n 不为 0 时循环执行, 最后得到最大公约数 5,再计算最小公倍数: 最小公倍数=两数的乘积/最大公约(因)数
perl -le '$a = $n = 20; $b = $m = 35; ($m, $n) = ($n, $m%$n) while $n; print $a*$b/$m'

得到最小公倍数 140.

使用 Math::BigInt 模块的 bgcd 和 blcm 计算最大公约数和最小公倍数:
[root@cz scripts]# perl -MMath::BigInt=bgcd -le 'print bgcd(20,35)'
5
[root@cz scripts]# perl -MMath::BigInt=blcm -le 'print blcm(20,35)'
140

11. 生成两数之间的随机数
先来生成10个处于 [5,15)之间的随机数:
perl -le 'print join ",", map{ int(rand(15 - 5)+5) } 1 .. 10'

int(rand(10)) 生成 0 ~ 9 之间的数字, 再加上5, 就可以生成 5 <= n < 15 之间的数字. 同理可以生成 x 个处于 [m,n)之间的数;

perl -le ‘print join “,”, map{ int(rand($n -- $m)+$m) } 1 .. $x’

12. IP 地址转换
(1) IP 地址转为整数:
# perl -le '$i = 3; $u += ($_ << (8*$i--)) for "127.0.0.1" =~ /(\d+)/g; print $u'
2130706433

"127.0.0.1" =~ /(\d+)/g 生成匿名数组, 包括元素 127, 0, 0, 1. ip地址分为4组, 每组8位, 通过 $_ << (8*$i--) 可以得到每组的转换值, 最后再求和就是 IP 地址转换后的整数. 另外因为每组8位, 可以将每组转换为2个16进制的数, 再通过 hex 函数得到十进制整数:
# perl -le '$ip="127.0.0.253"; $ip =~ s/(\d+)(?:\.|$)/sprintf("%02x", $1)/ge; print hex $ip'
2130706685

# perl -le '$ip="127.0.0.253"; $ip =~ s/(\d+)\.?/sprintf("%02x", $1)/ge; print hex $ip'
2130706685

第一行中的(?:\.|$) 表示值匹配不捕获, 这里可以匹配 127. 或最后一组数字 253, 但是只捕获 127 或 253, 通过 sprintf 转换后得到16进制 7f0000fd, 最后通过 hex 转为 10 进制.

同前面介绍的 unpack 函数, 对应 pack 函数, N 表示32位网络地址形式的无符号整形, 详见 perldoc pack.

# perl -le 'print unpack("N", 127.0.0.253)'
2130706685

上面的 127.0.0.253 是以版本字符(version string)表示的字符串, 是由特定序列值组成的字串值,比较特殊, 如果 IP 地址是以字符串的形式出现,需要先将其转为字节类型, 可以使用 Socket 模块的 inet_aton 函数:
# perl -MSocket -le 'print unpack("N", inet_aton("127.0.0.253"))'
2130706685
# perl -le 'print unpack("N", "127.0.0.253")'  # 转换错误
825374510

(2) 整数转换为 IP 地址
先使用 Socket 模块的 inet_ntoa转换:
# perl -MSocket -le 'print inet_ntoa(pack("N", 2130706685))'
127.0.0.253

这里先用 pack 将值改为字节顺序存储, 再通过 inet_ntoa 转换为 ip 地址. 也可以用位移的方式,如下:
# perl -le '$ip = 2130706685; print join ".", map{ (($ip>>8*($_))&0xFF) } reverse 0 .. 3'
127.0.0.253

reverse 0 .. 3 反转列表为 3 .. 0, map函数中, 第一次循环, $ip >> 24 后为 01111111,和 0xFF(二进制 11111111 ) 进行与操作后结果为 01111111(十进制127),第二次 $ip >> 16 后为 0111111100000000, 和 0xFF 进行与操作后结果为 00000000(十进制0), 后面以此类推, 最后得到 127.0.0.253


Perl one line command -- 字符串与数组

本章使用 Perl 命令行说明如何创建字符串和数组, 包括生成密码, 创建指定长度字符串, 查找字符串中的数值等, 也会介绍一些特殊变量比如 $, 和 @ARGV 等, 同样以示例说明.

1. 生成并打印字符

# perl -le 'print a..z'
abcdefghijklmnopqrstuvwxyz

在 Perl 中 .. 是范围操作符, 在列表环境中, 上述命令表示打印从 a 到 z 的字母, 也可以使用 $, 和 join 来指定字母之间的分隔符:
# perl -le  'print join ", ",(a..z)'
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
# perl -le  '$, = ", ";print (a..z)'
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z

不过对于下面示例, 在生成 z 之后, 会继续生成 aa, ab 直到 zz, 也可以指定 aa .. zz 生成 aa 到 zz 之间的字符:
perl -le  'print join ", ",(a..zz)'
perl -le  'print join ", ",(aa..zz)'

避免 strict 模块引起的问题, 字符用双引号引起来是更稳妥的做法, 比如:
# perl -le 'print "a".."z"'
abcdefghijklmnopqrstuvwxyz

2. 十六进制

# perl -le 'print join ", ",(0..9, "a".."f")'
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f

(0..9, “a”..”f”) 组成了一个大列表, 包含 0 到 f 字符, 对应了十六进制的字符, 如果要将 10 进制转为 16 进制,可以如下操作:
# perl -le '$num = 255; @hex = (0..9, "a".."f"); while($num) { $s = $hex[($num % 16)] . $s; $num = int $num/16 } print $s'
ff

$num % 16 的结果作为 @hex 数组的下标, 得到对应的十六进制字符, 如果用 printf 函数, 则更方便:
# perl -le 'printf("%x\n", 255)'
ff

使用 hex 函数可以很方便的将十六进制转为十进制:
# perl -le '$num = "ff", print hex($num)'
255

3. 生成指定长字符串

先回到 介绍 一章中, 之前有写过生成 8 位随机的字母密码串, 如下:
perl -le 'print map { ("a".."z")[rand 26] }1..8'

(“a”..”z”)[rand 26] 会执行 8 次, rand 26 随机生成 0 ~ 25之间的数字, 作为 (“a”..”z”)列表的下表, 最后打印出来 8 位长度的字符串。类似的, 我们可以使用 x 操作符重复执行多少字, 比如生成 50 个 a:
# perl -le 'print "a"x50'
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

"a" x50 属于字符拼接, 并不是列表环境, 不用使用 $, 等特殊符指定分隔符. 当然 -l 参数增加了换行,如果要严格输出指定的长度, 需要去掉 -l 参数。

4. 数组

可以使用 split 函数分割字符串, 返回需要的数组, 比如以下:
perl -le '@month = split " ", "Jan Feb Mar Apr ... Dec"'

数组下表 0 ~ 11 分别对应月份.也可以使用 qw/STRING/ 生成数组:
pelr -le '@month = qw/Jan Feb Mar Apr ... Dec/'

5. 命令行参数

# perl -le 'print "(", (join ",", @ARGV,),")"' v1 v2 v3
(v1,v2,v3)

@ARGV 包含所有的参数信息, $ARGV[0] 对应 v1, $ARGV[1] 对应 v2 , 基于这个特性, 可以生成一些 sql, 比如:
# perl -le 'print "insert into t values(" . (join ",", @ARGV) .");"' v1 v2 v3
insert into t values(v1,v2,v3);

6. ascii码值和字符串

使用 ord 函数可以得到字符对应的 ascii 码值, 如下所示, 将字符串分为单个字符后使用 ord 进行转换:
# perl -le 'print join ", ", map { ord } split //, "hello"'
104, 101, 108, 108, 111

也可以使用 unpack 进行转换, 如下:
# perl -le 'print join ", ", unpack("C*", "hello")'
104, 101, 108, 108, 111

C 表示 unsigned character, * 表示”hello”中的所有字符. 使用 sprintf 函数转为16进制:
# perl -le 'print join ", ", map{ sprintf "0x%x", ord $_ } split //, "hello"'
0x68, 0x65, 0x6c, 0x6c, 0x6f

根据上面的示例, 可以进行 ascii 码值转为字符串, 比如使用 unpack 对应的 pack 函数:
# perl -le 'print pack("C*", (104, 101, 108, 108, 111))'
hello

也可以使用 chr 函数:
# perl -le 'print join "", map chr, (104, 101, 108, 108, 111)'
hello

map chr, (104, 101, 108, 108, 111) 等同 map { chr } (104, 101, 108, 108, 111)

7. 生成基数组成的数组

# perl -le '$, = ", "; print @odd = grep { $_ % 2 == 1 } 1..100'
# perl -le '$, = ", "; print @odd = grep { $_ & 1 } 1..100'

两条命令等效, 都使用 grep 函数过滤满足条件的元素, 前者使用取模余 1 的方法, 后者采用按位与的方法,奇数的二进制最后一位肯定为 1, 则 $_ 为奇数时, $_ & 1 条件为真.

8. 计算字符串长度

# perl -le 'print length "hello"'
5

使用 length 函数返回字符串的长度.

9. 数组元素个数

可以使用数组下标和标量环境的方式得到数组元素的个数:
# perl -le '@array = ("a".."z"); print scalar @array'
26
# perl -le '@array = ("a".."z"); print $#array + 1'
26

$#array 表示数组 array 最后一个元素的下标。


Perl one line command -- 转义和替换

本章使用 Perl 命令行来更改, 转换, 替换文本内容, 同时会介绍 base64 的编解码, url 转义, HTMl转义等相关的信息。

1. ROT13

详见 http://en.wikipedia.org/wiki/ROT13
ROT13(回转13位,rotateby13places,有时中间加了个减号称作ROT-13)是一种简易的置换暗码,比如 A 加密后为 N, B 为 M, a 为 n, b 为 m.它是一种在网路论坛用作隐藏八卦、妙句、谜题解答以及某些脏话的工具,目的是逃过版主或管理员的匆匆一瞥, 本身上ROT13是它自己逆反;也就是说,要还原ROT13,套用加密同样的算法即可得,故同样的操作可用再加密与解密. 我们使用 y/// 和 tr/// 操作符说明如下:
perl -le '$string = "hello"; $string =~ y/A-Za-z/N-ZA-Mn-za-m/; print $string'
perl -le '$string = "hello"; $string =~ tr/A-Za-z/N-ZA-Mn-za-m/; print $string'

y 等同于 tr 操作符, tr/search/replace/ 表示转换 search 列表中的元素为 replace 列表中相同位置的元素, 比如 $string =~ tr/mn/op/ 表示将 $string 字符中的 m 转为 o, n 转为 p. 对于上述的示例, A-Za-z 中的每个字符被回转了13位. 再看 -i 参数的示例:
# cat file
how there are
list file
there are

# perl -pi.bak -e 'y/A-Za-z/N-ZA-Mn-za-m/' file

# cat file
ubj gurer ner
yvfg svyr
gurer ner

-i 参数指定了 .bak 扩展, 在程序执行前, 会将 file 改为 file.bak 文件达到备份的目的, 再将生成的结果输出到 file, 通过 file.bak 和 file 文件状态的 Modify 信息可以确定 -i 参数的处理逻辑.

2. Base64

可以使用 MIME::Base64 模块的 encode_base64 和 decode_base64 方法对字符串进行编码和解码操作, 如下:
# perl -MMIME::Base64 -e 'print encode_base64("hello")'
aGVsbG8=
# perl -MMIME::Base64 -e 'print decode_base64("aGVsbG8=")'
hello

想要对整个文本文件进行编码, 可以使用前面章节提到的 -00(按段落读取内容) 和 -0777(整个文本读取) 参数.

3. 转义/反转义字符串
可以使用 URI::Escape 模块的 uri_escape 和 uri_unescape 方法对 uri 进行转义/反转义,如下:
# perl -MURI::Escape -le 'print uri_escape("http://zhechen.me")'
http%3A%2F%2Fzhechen.me

# perl -MURI::Escape -le 'print uri_unescape("http%3A%2F%2Fzhechen.me")'
http://zhechen.me

4. HTML编码
HTML编码可以将一些标签信息转换为 HTML 编码的形式, 比如 < 转换为 < 等, 可以使用 HTML::Entities 模块的 encode_entities 和 decode_entities 方式实现:
# perl -MHTML::Entities -le ‘print encode_entities(““)’
<html>
# perl -MHTML::Entities -le ‘print decode_entities(“<html>”)’

5. 大小写转换
先来将文本内容全转为大写,使用 uc 函数或将 \U加到每行的开头:
# cat file
ubj gurer ner
yvfg svyr
gurer ner

[root@z6 scripts]# perl -nle 'print uc' file
UBJ GURER NER
YVFG SVYR
GURER NER

# perl -le 'print "\Uhello"'
HELLO

小写转换可以使用 lc 函数或将 \L加到每行的开头, 如果只让每行开头的字母大写, 可以先将整行都转为小写,再转第一个字符为大写:
# perl -le 'print ucfirst lc "hello World"'
Hello world
# perl -le 'print "\u\Lhello World"'
Hello world

使用 y/// 或 tr/// 操作符可以将字符串中的大小写互换, 比如:
# perl -le '$string = "Hello World"; $string =~ y/A-Za-z/a-zA-Z/; print $string'
hELLO wORLD

使用正则匹配让每个单词的首字母大写:
# perl -le '$string = "hello world"; $string =~ s/(\w+)/\u$1/g; print $string'
Hello World
# perl -le '$string = "hello world"; $string =~ s/(\w+)/ucfirst $1/ge; print $string'
Hello World

6. 去除空白符
去除每行开头的空格或制表符:
# perl -ple 's/^[ \t]+//' file
# perl -ple 's/^(?: |\t)+//' file
# perl -ple 's/^\s+//' file

[ \t] 和 (?: |\t) 等效, ^表示每行开头, 表示空格或制表符, \s 则可以匹配所有的空白符, 包括 tab, 水平制表符等。同理可以去除每行末尾的空格或制表符:
# perl -ple 's/[ \t]+$//' file
# perl -ple 's/(?: |\t)+$//' file
# perl -ple 's/\s+$//' file

符号 $ 表示每行结尾。

7. 换行
从 UNIX 换行改为 Windows 换行:
perl -pe 's|\012|\015\012|' file
perl -pe 's|\n|\r\n|' file

由于平台的关系,CR(\015) 对应 \r, LF(\012) 对应 \n, Linux 到 Windows 的转换通常用第二种命令,不过第一种方式更通用些. Windows 到 Unix 刚好相反, 从 CRLF 到 LF 转换。Mac 系统中通过 \015(CR) 作为换行符, 如果从 Unix 转到 Mac, 可以使用:
perl -pe 's|\012|\015|' file

8. 内容替换
perl -pe 's/foo/bar/' file     # 替换每行中第一个匹配的 foo 为 bar
perl -pe 's/foo/bar/g' file    # 替换每行中的 foo 为 bar
perl -pe 's/foo/bar/ if /baz/' # 该行匹配 baz, 则替换 foo 为 bar
perl -pe '/baz/ && s/foo/bar/' # 同上

如果要按照段落反转文件的内容, 使用 -00 按照段落读取, 再通过 reverse 反转:
perl -00 -e 'print reverse <>' file

符号 <> 表示从标准输入读取内容.
上面的 reverse 反转列表中的元素, 在标量环境中, $_ 包含了整行的内容, 如果进行反转, reverse 会将整行内容当做一个元素反转, 反转后第一个字符就到最后一个, 第二个到倒数第二个,一次类推, 不管 reverse 在标量还是列表环境中, 都返回列表值, 所以如果要反转一行的内容并打印出来需要在标量环境中打印:
perl -lne 'print scalar reverse $_' file

符号 $_ 可以忽略, 根据这个示例我们也可以将行中的每列数据进行反转:
perl -alne 'print "@{[reverse @F]}"' file
perl -alne '$" = " "; print "@{[reverse @F]}"' file
perl -alne '$, = " "; print reverse @F' file

上面三条命令等效. $" 类似 $, 只不过 $" 适用于数组或数组切片, 默认为空格, $, 则是 print 操作的输出分隔符, 默认为 undef, 所以第一条和第二条命令是一样的。


Perl one line command -- 输出和删除行

本章介绍使用 Perl 命令行输出和删除指定的行内容, 比如 输出/删除 指定的行, 重复的行, 匹配的行等。输出和删除操作是相对的( -i 参数 ), 明白了如何输出, 删除也就尽在掌握.

1. 输出文本 n 行内容
perl -ne 'print ; exit' file
perl -i -ne 'print ; exit' file

print 省去了 $_ 变量, 打印第一行后通过 exit 退出, 这条命令实现了打印文本的第一行信息, 等同 head -1 file, 第二条命令等同第一条, 但是通过 -i 参数实现了删除其它行操作, 最后的 file 只保留第一行内容; 如果要在删除之前进行备份, 可以给 -i 指定扩展名, 如 .bak 等. 后续的示例不在说明删除操作, 只列举输出操作。如果要打印文本的前 10 行内容, 可以通过行号实现, 下面几条命令等效:
perl -ne 'print if $. <= 10' file
perl -ne '$. <= 10 && print' file
perl -ne 'print if 1..10' file
perl -ne 'print; exit if $. == 10' file

第三条命令中, 使用了 .. 范围操作符, 在标量环境中返回布尔值, 这种操作相当于双重的判断(flip-flop), 类似于 sed, awk 中的行范围分隔符(,) , 1..10 为真时, 打印行内容, 为 false 则不打印。如果需要打印最后一行内容, 比起上面的示例则要麻烦一些, 因为不清楚哪一行才是结尾, 所以只能不停的读取, 直到最后一行; 或者使用 eof 函数判断文件的结尾:
perl -ne '$last = $_; END{print $last}' file
perl -ne 'print if eof' file

基于上述示例, 再来打印最后的十行内容, 类似 tail -n 10 file:
perl -ne 'push @a, $_; @a = @a[@a-10 .. $#a] if @a>10; END{ print @a }' file
perl -ne 'push @a, $_; shift @a if @a > 10; END{ print @a }' file

第一条命令使用数组切片的方式取得最后的十行内容, $#a 表示数组 @a 最后一个元素的下标, @a-10 在标量环境中表示数组 @a 的元素数量减去 10, 总体上表示取数组 @a 后十个元素, 再重新赋值给数组 @a, 如果数组元素数量小于 10,则不用做处理. 第二条命令换了一种方法, 每次判断元素数量大于 10 的时候就删除数组中的第一个元素.

2. 打印匹配/非匹配行
还是通过正则实现:
perl -ne '/there/ && print' file
perl -ne 'print if /there/' file

如果打印非匹配行, 可以使用以下:
perl -ne '!/there/ && print' file
perl -ne 'print if !/there/' file
perl -ne 'print unless /there/' file
perl -ne '/there/ || print' file

最后一条命令的 || 操作符表示逻辑或, 为假时则执行 print 操作.

3. 打印匹配行的前/后一行
如果需要保存匹配行的前一行, 需要有一个变量保存前一行的内容, 下一行匹配的时候则输出:
# cat file
novel
film
group
# perl -ne '/film/ && $pren && print $pren; $pren = $_' file
novel

在最开始的时候 $pren 未定义, 则不执行 print, 同时将第一行内容赋值给 $pren, 下一次循环的时候, 如果正则匹配上则 /there/ && $pren 条件为真, 执行 print, 这时候的 $pren 为上一行的内容; 如果正则为假, 则不执行, 同时将第二行内容赋值给 $pren, 以此类推. 该命令等同 grep -B 'there' file, 不过 grep 也输出了匹配行。如果要打印匹配的后一行, 启用一个布尔变量标识即可:
# perl -ne 'if($p) { print; $p = 0} $p++ if /film/' file
group
# perl -ne '$p && print; $p = /film/' file

如果当前行匹配, $p 变量这时候为真, 下一次循环的时候则执行打印操作, 同时重置 $p 变量为下次匹配做准备,打印既匹配 AAA 也匹配 BBB 的行:
perl -ne '/AAA/ && /BBB/ && print' file

如果不匹配 AAA 也不匹配 BBB, 可以使用以下:
perl -ne '!/AAA/ && !/BBB/ && print' file

如果要按顺序匹配 AAA 和 BBB, 并且 BBB 在 AAA 之后, .* 表示0或多个字符:
perl -ne '/AAA.*BBB/ && print' file

4. 行长度
perl -ne 'print if length >= 80' file
perl -ne 'print if length <= 80' file

打印长度大于/小于 80 的行.

5. 打印指定行号
perl -ne 'print if $. == 10; exit' file    # 打印第 10 行
perl -ne 'print if $. != 10' file          # 忽略第 10 行
perl -ne 'print if $. >= 10 && $. <= 20'   
perl -ne 'print if 10..20'

3, 4 条命令等效, 都打印 10 ~ 20 行。如果要匹配两个正则之间的行, 也可以使用 .. 操作符, 匹配 /there1/ 的时候条件为真, 直到匹配 /there2/, 类似于上面的 10..20, 如下:
perl -ne 'print if /there1/../there2/' file

打印只含字母的行, 可以使用 [[:alpha:]] 来匹配, [[:alpha:]]+ 表示一个或多个字母:
perl -ne 'print if /^[[:alpha:]]+$/' file

6. 打印最长/最短行
perl -ne '$p = $_ if length($_) > length($p); END{ print $p} ' file
perl -ne '$p = $_; $p = $_ if length($_) < length($p);END{ print $p }' file

第一条命令打印最长行, 第二条命令打印最短行, 最短行中的 $p 变量需要初始化, 否则 $length($_) < $length($p) 永远为假。

7. 打印重复或唯一行
可以使用哈希来实现该功能, 哈希的键为行内容, 值为出现的次数, 如下:
perl -ne 'print if ++$a{$_} > 1' file
perl -ne 'print if ++$a{$_} == 2' file
perl -ne 'print unless $a{$_}++' file

第一条命令会重复打印, 第二条命令只将重复的行打印一次; 第三条命令是相对的, 当行出现多次的时候, $a{$_} 为 1, 则不执行 print。


Perl one line command -- 常用正则

本章说明一些常用的正则表达式, 比如匹配 IP 地址, HTTP 头信息, email 地址等.

1. 匹配 IPv4 地址
IP 地址格式 xxx.xxx.xxx.xxx, 使用 \d 来匹配数字,通用的做法如下:
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/

{1,3}表示匹配最少一个,最多3个数字,这个表达式没有检查地址的有效性(不能大于255),所以也能匹配出无效的地址,但是对于有效的地址都能匹配出来; 我们可以发现前三部分是一样的, 可以改成:
/^(\d{1,3}\.){3}\d{1,3}$/

{3}表示匹配3次,如果要检查地址的有效性, IPv4的范围是 0.0.0.0 ~ 255.255.255.255, 每字节可以是 1,2,3 位整数, 如果是 1 位的时候可以使用 [0-9] 来匹配 0 ~ 9, 如果是 2 位可以使用[0-9][0-9] 来匹配 10~99, 如果是 3 位 , 则需要匹配 100 ~ 255 之间的数, 可以使用 1[0-9][0-9] 匹配 100 ~ 199 之间的数, 2[0-4][0-9] 匹配 200 ~ 249 之间的数, 25[0-5] 匹配 250 ~ 255 之间的数.可以使用 | 或操作将这些正则连接起来, 整个表达式如下:
/^([0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/

所以整体上匹配一个有效的 IP 地址的代码如下:
$ip_re = qr/[0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]/;
if ( $ip =~ /^$ip_re\.$ip_re\.$ip_re\.$ip_re$/ ) {
   print "$ip\n";
}

qr 表示引用正则相关的表达式: Regexp-like quote.

2. 匹配邮件地址
一个正常邮件的的形式如下 1234mnop@qq.com, 可以使用以下表达式匹配:
/\S+@\S+\.\S+/

\S排除了出现空白字符的可能, 既匹配了 @ 符号, 也匹配了 . 符号, 至少看起来满足这个条件的都是正常的邮件, 也可以使用 Email::Valid 模块进行检查, 不过需要额外安装:
perl -MEmail::Valid -ne 'print Email::Valid->address("1234mnop@qq.com") ? "valid email" : "invalid email"' file

? : 为 3 元操作符, address 为真则打印 valid email, 否则打印 invalid email.

3. 检查数字
如何检查数字的有效性, 先来看看有效的数字有哪些:
23                          # 整数
23.1                        # 小数
+23, -23, +23.1, -23.1      # 正负数
1,000                       # 整数

如果再算上复数, 十六进制, 八进制等就更复杂了, 如果是简单的匹配:
/^\d+$/               # 匹配多个数字
/^[+-]?\d+$/          # 正负数, ? 表示可选, 匹配 0 个或 1 个
/^[+-]?\d+\.?\d*$/    # 小数

上面的表达式并没有匹配类似 1,234,456 或 .3 的数字. 可以使用 Regexp::Common 模块来实现匹配:
perl -MRegexp::Common -ne 'print if /$RE{num}{real}/' file

如果要匹配十六进制和八进制, 可以使用以下正则:
/^0x[0-9a-f]+/i
/^0[0-7]+/

i 表示忽略大小写.

4. 检查出现两次的单词
/(words).*\1/

() 用来捕获里面的内容, 并赋值到 1 里, \1 是对 1 的解引用, 表示捕获到的内容. 整个表达式匹配 words.*words

5. 整数加1
$sth =~ s/(\d+)/$1+1/ge

g 为全局匹配, e 表示可以执行代码, ()捕获的内容放到 $1 中, 最有使用 $1+1 替换匹配的整数。

6. 匹配 HTTP 头信息
用 curl 来请求 weibo 的信息, 如下
# curl -I http://www.weibo.com/
HTTP/1.1 302 Moved Temporarily
Server: WeiBo
Date: Wed, 04 Mar 2015 06:26:32 GMT
Content-Type: text/html
Connection: close
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: Wed, 04 Mar 2015 06:26:32 GMT
......

匹配各个字段都比较容易, 将捕获的内容放到特殊变量 $1 中, 比如:
date: Wed, 04 Mar 2015 06:29:33 GMT

7. 匹配可打印的 ascii 码字符
http://zh.wikipedia.org/wiki/ASCII
详见维基百科, 除去控制字符, 剩下的为可显示字符, 可以看到可显示字符的范围是 空格 到 符号 ~, 对应16进制的 0x20 ~ 0x7e, 了解这些之后, 就可以明白下面表达式的意思:
/[ -~]/

表示匹配空格到符号 ~ 之间的字符, 如果不匹配可打印字符, 使用 ^ 反向即可:
/[^ -~]/

8. 替换标签信息
如果要将 或 替换为

, 可以使用以下表达式:
$str =~ s#<(/)?b>#<$1p>#g

(/)? 表示匹配并捕获 0 或 1 次, 并赋值给 $1 变量, g表示全局替换。

9. 提取匹配的内容
如下表达式:
@match = $str =~ /regex/g;
@match = ($str =~ /regex/g);

$str =~ /regex/g 匹配所有相关的内容, 返回一个列表, 可以通过数组保存匹配到的内容, 如下所示匹配所有的整数:
# perl  -le '$, = " "; $str = "hell 253, yes 50"; @match = ($str =~ /\d+/g); print @match'
253 50

也可以匹配键值对, 比如字符串"key1=v1; key2=v2,v21; key3=v3,vs3":
# echo "key1=v1; key2=v2,v21; key3=v3,vs3" | perl -lne '@vals = $_ =~ /[^=]+=([^;]+)/g; print "@vals"'
v1 v2,v21 v3,vs3

表达式首先匹配 [^=]+ 即不是 = 的字符串, 再匹配 = , 再匹配 [^;]+ 即非 ; 的字符串, 再将结果存到数组中。