Perl one line command – 计算
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, 综上 1x5 满足了不匹配两个正则的条件, 所以它是素数.
2. 计算一行中各列数的和
echo "1 5 7" | perl -MList::Util=sum -alne 'print sum @F'
同 http://arstercz.com/perl-one-line-command-%E4%BB%8B%E7%BB%8D/提到的 -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 进制. 同 http://arstercz.com/perl-one-line-command-%E4%BB%8B%E7%BB%8D/中介绍的 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-LINERS Copyrught @ 2014 by Peteris Krumins ISBN-10: 1-59327-520-X