Files
devops/perl/p1linebook.md
2025-09-17 16:08:16 +08:00

1572 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Perl 单行中文版
## 主要内容
- [Perl命令行参数](#perl命令行参数)
- [01 文件行距](#文件行距)
- [04 行计数](#行计数)
- [03 数值计算](#数值计算)
- [04 字符和数组生成](#字符和数组生成)
- [05 文本转化和替换](#文本转化和替换)
- [06 有选择的打印或者删除行](#有选择的打印或者删除行)
- [07 正则表达式](#正则表达式)
## 日积月累
1、 转php的序列化格式到json: `echo 'O:8:"stdClass":4:{s:7:"has_alt";b:0;s:8:"has_ctrl";b:1;s:9:"has_shift";b:0;s:8:"key_code";i:192;}'|perl -lpe 's#.*{#{#;s#s:\d+:"([^"]+)";[bi]:([^;]+);#\1 \2;#g;'`
结果:
```
{
has_alt 0;
has_ctrl 1;
has_shift 0;key_code 192;
}
```
2、perl oneline统计源码行数去除行式注释
lib目录下 pm文件行数
perl -lne '$a++ unless /^#/ and /\/\// and /=/; END{print $a} ' `find lib/ -type f -name "*.pm"`
3、基因序列去重
读入基因id的建立一个散列然后从genome.fasta找散列中存在的基因如果存在就打印出来并存储
perl -ne 'if(/^>(\S+)/){$c=$i{$1}}$c?print:chomp;$i{$1}=1 if @ARGV' ids.file genome.fasta > ids.fasta ids.file
## 来源
* <https://github.com/bollwarm/perlonelinecn>
* <http://ijz.me/?tag=perl-one-liners>
* <http://www.catonmat.net/download/perl1line.txt>
* <http://www.catonmat.net/blog/perl-book/>
* <http://www.perl.com/pub/a/2004/08/09/commandline.html>
##Perl命令行参数
Perl有很多命令行参数。通过它们, 我们有机会写出更简单的程序。在这篇文章里我们来了解一些常用的参数。
### 第一部分 Safety Net Options 安全网参数
在使用Perl尝试一些聪明(或stupid)的想法时, 错误难免会发生。有经验的Perl程序员常常使用三个参数来提前找到错误所在,
- -C 这个参数编译 Perl 程序但不会真正运行它。
而只是检查所有语法错误。每次修改 perl 程序之后我都会立刻使用它来找到任何语法错误。
perl -c program.pl
- -W 它会提示你任何潜在的问题。
Perl 5.6.0之后的版本已经用use warnings; 替换了-w 。你应该使用 use warnings 因为它要比 -w 更灵活。
- -T 它把perl放到了tain模式。
在这个模式里, Perl 会质疑任何程序外传来的数据。例如,从命令行读取, 外部文件里读取或是 CGI 程序里传来的数据。
这些数据在 -T 模式里都会被 Tainted 掉。
### 第二部分:命令行Perl参数可以让短小的Perl程序运行在命令行。
- -e 可以让Perl程序在命令行上运行。
例如, 我们可以在命令行上运行 “Hello World” 程序而不用把它写入文件再运行。
perl -e 'print "Hello World\n"'
多个 -e 也可以同时使用, 运行顺序根据它出现的位置。
perl -e 'print "Hello ";' -e 'print "World\n"'
像所有的 Perl 程序一样, 只有程序的最后一行不需要以 ; 结尾。
- -M 可以像通常一样引用模块
perl -MLWP::Simple -e 'getstore ("http://www.163.com/","163.html")' ##下载整个网页
`-M+`模块名和 use模块名一样
### 第三部分:隐式循环
- -n 增加了循环的功能, 使你可以一行一行来处理文件
perl -n -e 'print;' 1.txt
这与下面的程序一样。
while (<>) {
print;
}
`<>`打开命令行里的文件,一行行的读取。每一行缺省保存在`$_`
perl -n -e 'print "$. - $_"' file
上面的这一行可以写成
while (<>) {
print "$. - $_"
}
输出当前行数`$.`和当前行`$_`
- `-p`,和`-n`一样,但是还会打印`$_`的内容
如果想在循环的前后做些处理, 可以使用`BEGIN``END block`。下面的这一行计算文件里的字数。
perl -ne 'END { print $t } @w = /(\w+)/g; $t += @w' file.txt
每一行所有匹配的字放入数组`@w`, 然后把`@w`的元素数目递加到`$t`.`END block`里的`print`最后输出文件总字数。
还有两个参数可以让这个程序变得更简单。
- -a 打开自动分割 (split)模式。
空格是缺省的分割号。输入根据分割号被分离然后放入缺省数组@F
使用-a上面的命令可以写成这样
perl -ane 'END {print $x} $x += @F' file.txt ##使用了-a
- -F 把缺省的分割号改为你想要的。
例如把分割号定为非字符,上面的命令可以改为:
perl -F'\W' -ane 'END {print $x} $x += @F' file.txt
下面通过Unix password 文件来介绍一个复杂的例子。 Unix password 是文本文件, 每一行是一个用户记录,
由冒号:分离。第 7 行是用户的登录 shell 路径。我们可以得出每一个不同 shell 路径被多少个用户使用 :
perl -F':' -ane '$s{$F[6]}++;' \>; -e 'END { print "$_ : $s{$_}" for keys %s }' /etc/passwd
虽然现在不是一行, 但是你可以看出使用参数可以解决什么问题。
### 第四部分Record Separators 数据分隔符
- $/ 和 $\ — 输入,输出分隔号。
- $/ 用来分隔从文件句柄里读出的数据, 缺省 $/ 分隔号是 \n , 这样每次从文件句柄里就会一行行的读取
- $\ 缺省是空字符, 用来自动加到要 print 的数据尾端。这就是为什么很多时候 print 都要在末尾加上 \n。
- $/ 和 $\ 可与 -n -p 一起使用。在命令行上相对应为 -0 (零) 和 -l ( 这是 L )。
- -0 后面可以跟一个 16 进制或8进制数值, 这个值用来付给 $/ 。
- -00 打开段落模式, -0777 打开slurp 模式 (即可以一次把整个文件读入) , 这与把 $/ 设为空字符和 undef 一样效果。
单独使用 -l 有两个效果:
第一,自动 chomp 输入分隔号;
第二,把$/ 值付给 $\ (这样 print 的时候就会自动在末尾加 \n )。
- -l 参数, 用来给每一个输出加`\n`。例如
perl -le 'print "Hello World"'
### 第五部分:原位编辑
使用已有的参数我们可以写出很有效的命令行程序。常见的Unix I/O 重定向:
perl -pe 'some code' < input.txt >> output.txt
这个程序从 input.txt 读取数据, 然后做一些处理再输出到 output.txt. 你当然也可以把输出重定向到同一个文件里。
上面的程序可以通过 -i 参数做的更简单些。
- -i 把源文件更名然后从这个更名的源文件里读取。
最后把处理后的数据写入源文件。如果 -i 后跟有其他字符串, 这个字符串与源文件名合成后来生成一个新的文件名。
此文件会被用来储存原始文件以免被 -i 参数覆盖。
这个例子把所有 php 字符替换为 perl :
perl -i -pe 's/\bPHP\b/Perl/g' file.txt
程序读取文件的每一行, 然后替换字符, 处理后的数据重新写入( 即覆盖 ) 源文件。
如果不想覆盖源文件, 可以使用
perl -i.bak -pe 's/\bPHP\b/Perl/g' file.txt
这里处理过的数据写入 file.txt , file.txt.bak 是源文件的备份。
### perl经典的例子
问题:遇到一问题:
aaa@domain.com 2
aaa@domain.com 111
bbb@home.com 2222
bbb@home.com 1
类似这种输出,我想把他们变换成下面形式:
aaa@domain.com 113
bbb@home.com 2223
就是将相同邮箱名称后面的数字相加。各位大侠能否给些思路如何用perl来实现。答案
perl -lane '$cnt{$F[0]}+=$F[1];END{print "$_\t$cnt{$_}" for keys %cnt}' urfile
如果熟悉了上面几个perl命令行参数的用法上面的这个命令应该很好理解
每次读取urfile的一行由于使用了`-a`,打开自动分割(split)模式。空格是缺省的分割号。
输入根据分割号被分割然后放入缺省数组`@F`中,以文件的第一行为例子`$F[0]`就是`aaa@domain.com`, `$F[1]`是2,
`$cnt{$F[0]} +=$F[1]`是一个哈希, 以`$F[0]`为键,`$F[1]`为值,把相同key的数值都叠加起来.然后把文件的每一行都这样处理一次。
`END{}`就是在循环完之后再处理。里面的意思就是打印这个`%cnt`哈希。这个哈希数组的健就是邮箱名称,值就是叠加和。
下面的是上面行命令的文本形式:
#!/usr/bin/perl
use strict;
use warnings;
my %hash;
while (<>){
chomp;
my @array=split;
$hash{$array[0]} +=$array[1];
}
END{
foreach (keys %hash){
print"$_\t$hash{$_}\n";
}
}
#文件行距
## 1.双倍文件行距
perl -pe '$\="\n"'
这行代码双倍文件的行距。这行代码中有三个地方我们要说明一下:“-p”,“-e”选项以及“$\”变量。
首先介绍“-e”选项。 “-e”选项用来在命令行的形式直接执行perl代码。通常用于执行一个很轻便程序的而懒得创建perl源文件的时候。通过用“-e”你就可以非常方便在命令执行代码。
接着说“-p”选项。“-p”选项用于假定perl代码运行于如下的一个循环环境中
while (<>) {
# 此处为的perl代码
} continue {
print or die "-p failed: $!\n";
}
这个结构遍历所有输入,执行你的代码并且输出“$_”的值。通过这种方式你可以有效的修改全部或者部分输入值。“$_”变量可以当成一个匿名变量用来盛放好东东。
“$\”变量和awk中的ORS类似。附加在每一个打印操作后。如果没有任何的打印参数则直接打印出“$_”的内容好东东
在本例这行代码中“-e”执行的代码是 $\="\n",代码等价于:
while (<>) {
$\ = "\n";
} continue {
print or die "-p failed: $!\n";
}
实际执行的流程是,先读一行,并把它存储到“$_”中包括当前行的换行符“$\”被设置为换行符,并且调用打印函数。由于没有赋予参数,打印函数输出“$_”的内容“$\”被附加到了最后。结果就是每一行无误修改的打印出来,同时后面跟着被赋值为换行符的“$\”。效果就是输入的文件被双倍行距了。
实际上不必要把每一行都设置“$\”为换行符。这可能最短的双倍文件行距的一行代码了。其他同样效果的代码有:
perl -pe 'BEGIN { $\="\n" }'
这行代码是在Perl执行之前设置“$\”为换行符BEGIN块是在任何代码执行之前先执行
perl -pe '$_ .= "\n"'
这行代码相当于:
while (<>) {
$_ = $_ . "\n"
} continue {
print or die "-p failed: $!\n";
}
代码附加一个额外的换行符给每一行,然后输出这行。
当然最简洁最酷的大概使用“s///”替换符了:
perl -pe 's/$/\n/'
用换行符替换了正则表达式符号“$”,它匹配的是每一行的结束。这就相当于在行结尾增加了一个换行符。
## 2.双倍除了空行以外其他行的行距。
perl -pe '$_ .= "\n" unless /^$/'
这个行代码双倍所有非空行行间距。它通过给每一个非空行添加一个换行符的方式实现。“unless”意思是“if not”。“unless /^$/”意思是“如果不是开始即结束的行”。条件“开始即结束的行”,实际上只有包含一个换行符的行。
把它扩展开来看的话:
while (<>) {
if ($_ !~ /^$/) {
$_ .= "\n"
}
} continue {
print or die "-p failed: $!\n";
}
一个更好的例子是把包含空格和tab建的行都算在里面
perl -pe '$_ .= "\n" if /\S/'
这个代码每一行匹配“\S”. “\S”是一个正则表达式符和“\s”的意思想反。“\s”是匹配所有的空字符。这样代码就是匹配至少含有一个非空字符tab竖直tab空格等等的行使其双倍行距。
## 3.三倍行距
perl -pe '$\="\n\n"'
或者
perl -pe '$_.="\n\n"'
代码和#1例子相似,除了在每一行最后添加两个换行符。
## 4.N倍行间距
perl -pe '$_.="\n"x7'
这个行代码在每一个行后面插入7个换行符。我们注意到使用的是"\n" x 7来重复7次换行符的意思。“x”符和随后的N一起表示重复N次的意思。
例如:
perl -e 'print "foo"x5'
将会输出"foofoofoofoofoo"。
## 5.给每一行之前增加一个空白行。
perl -pe 's//\n/'
这个行代码使用"s/pattern/replacement/"的替换符。他匹配“$_”变量中的第一个模式正则表达式并且用replacement替换掉。本例中模式为空意味着匹配字符建的任何位置本例中是第一个字符之前的位置并且用换行符替换它。影响就是插入一个换行符在每行之前。
## 6.删除所有空行
perl -ne 'print unless /^$/'
本行代码使用“-n”标志。-n”标志会假定perl在下循环的中执行你的代码
LINE:
while (<>) {
#你的代码在这执行
}
其执行过程是:通过过“<>”读出每一行,并且把它存到变量“$_”中。这时你可以执行任何你想执行的。你可以在你主程序文本中指定他。
LINE:
while (<>) {
print unless /^$/
}
更进一步展开:
LINE:
while (<>) {
print $_ unless $_ =~ /^$/
}
这行代码输出所有非空行。其他形式地同功能代码:
perl -lne 'print if length'
这行代码使用了“-l”命令行参数。“-l”自动chomp输入行主要是删除了输入行最后的换行符。接着测试行的长度。如果有任何字符测试就为真就打印出这行。
perl -ne 'print if /\S/'
这个行代码功能和“print unless /^$/”大有不同后者会打印出包含空格tabs的行而前者不会。
## 7.删除连续的空行,只留下一行。
perl -00 -pe ''
这代码很神奇对不?首先,他没有任何执行代码,-e执行部分为空。其次它有一个看起来傻傻的“-00”命令行选项这个选项开启大段落模式。一个段落是指两个换行符之间的文本。所有其他换行符将被忽略。段落被存储到“$_”中“-p”选项吧他打印出来。
后来我有发现一个更短版本行代码。
perl -00pe0
## 8.把所有的空行定制为固定N个连续空行。
perl -00 -pe '$_.="\n"x4'
这个行代码综合了上一个代码8#4的代码。他开启了大段落模式接着添加N-1个换行符。在本例代码中指定行间距为倍行距“\n”×4打印4次加上本身自带一个段落换行符总共5个
# 行计数
## 9.给文件打印行号。
perl -pe '$_ = "$. $_"'
和一个行代码中说明一样,“-p”作用在让Perl在命令行开启循环执行代码通过”-e”指出循环的读出输入的每一行存储在“$_”变量中执行代码接着打印出“$_”内容。
本例中我们仅仅通过修改“$_”在其前附加“$.”的内容。特定变量“$.”保存当前输入行的行号。
结果就是每一行标上了行号。
## 10.仅给非空行打印行号
perl -pe '$_ = ++$a." $_" if /./'
这行代码我们是使用了“行为if条件”语句当条件为真的时候执行行为。本例中条件是一个正则表达式“/./”,用来匹配除换行符外任何一个字符(就是匹配非空行);行为是“$_=++$a.”“_$”,由变量“$a”值在每行增加1,并附加到当前行前面。由于没有设置strict 编译,$a无需事先定义。
本语句执行结果是每一个非空行$a值1并附加到行前面。对每个空行没有任操作只输出空行。
## 11.打印文件非空行及其行号(删掉空行)
perl -ne 'print ++$a." $_" if /./'
和上例相比,本例用“-n”代替了上“-p”两者都执行行循环唯一区别是“-p”自动会打印“_$”的内容,而-n不打印所以需要用print显式打印出需要打印的内容。
由于正则不会匹配空行,所以对空行不会执行打印动作,所以就会跳过空行。
## 12.给所有行标行号,但是只打印非空行的行号
perl -pe '$_ = "$. $_" if /./'
这行代码和例10相似。我们只调整“$_”变量仅仅在匹配正则至少一个非换行字符非空行时附加行号($.)。所有其他行打印时候,没有处理,无行号。
## 13.给匹配模式行标行号,其他未匹配的仅打印。
perl -pe '$_ = ++$a." $_" if /regex/'
本行代码也是用了“行为if条件”语句但是条件是一个模式正则表达式“/regex/”。行为表现和例10一样。这儿不再赘述。
## 14.仅给匹配的行标行号和打印。
perl -lne 'print "$. $_" if /regex/'
本例和例11基本上一样唯一不同是打印匹配模式“regex/”的行。
## 15.给所有行标行号,但是打印匹配模式行的行号。
perl -pe '$_ = "$. $_" if /regex/'
本例和上一列以及例12类似。在这儿当匹配漠视的时候附加行号到当前行否则仅打印而无附加行号。
## 16.给文件标格式化的行号(模仿 cat -n
perl -ne 'printf "%-5d %s", $., $_'
这个行代码使用带格式化的“printf”打印函数打印行号。在本例中为左对齐5字符长度。
其他比较好的格式是“%5d” 表示右对其5个字符长度。“%05d”表示右对齐5字符长度用0填充长度5用00005表示
## 17.打印文件行的数量(模仿 wc -l
perl -lne 'END { print $. }'
这行代码使用“END”语句块这个语句块表现和awk中的基本一样在整个程序执行最后时候执行。本例中用“-n”参数隐藏了输入的循环过程。在所有输入循环结束后特殊变量“$.”保存了输入过的行号。“END”语句块打印出了这个变量。“-l”参数设置输出记录分割符为换行符这样我们就不必去打印“$.\n”
另一种实现方式是:
perl -le 'print $n=()=<>'
这是比较费解的一个如果你知道Perl 上下文的就好理解多了。本行代码中“()=<>”部分是个在列表上下文中的<>操作符,<>操作符会读整个文件所有行。同时,在标量上下文中,“$n”取得数量。这样“$n=()=<>”结构就等与输入的行数也就是文件的行数。print语句打印出这个数。“-l”参数使得输出数量后输出一个换行符。
下面这行代码表现也一样,除了长度不同外:
perl -le 'print scalar(()=<>)'
完全显式的版本是:
perl -le 'print scalar(@foo=<>)' 另一种方式是perl -ne '}{print $.'
这行代码使用了eskimo操作符“}{”和“-n”命令行参数。在例11中解释过“-n”参数强制Perl执行一个“while(<>){}”循环。 eskimo操作符强制Perl跳过这个循环,程序的实际执行是:
while (<>) {}{ # eskimo操作符 print $.;}
这很明显就是执行所有输入循环后,打印出“$.”,这就是行号。
## 18.打印一个文件中的非空行数
perl -le 'print scalar(grep{/./}<>)'
这行代码使用“grep”函数他的和Unix命令行同名工具功能相近。对给予的列表值“grep {condition}”返回匹配条件的值。本例中条件是一个匹配至少一个字符的正则表达式所以输入行被筛选了“grep{/./}”返回所有的非空行。为了得到列表的数量我们使用scalar函数强制其为标量上下文中这样就得到列表数量。最后打印出其结果。
一个更精妙版本的行代码还可以用“~~”代替scalar(),这能更短一点:
perl -le 'print ~~grep{/./}<>'
这还可以更短:
perl -le 'print~~grep/./,<>'
## 19.打印文件中空行的数量
perl -lne '$a++ if /^$/; END {print $a+0}'
本行代码中使用变量$a计算多少次空行结束循环后在END块打印$a值。最后输出时使用了“$a+0”结构确保文件中没有空行是打印出0。
我们也可以修改上一例的代码:
perl -le 'print scalar(grep{/^$/}<>)'
“~~”代替后的版本:
perl -le 'print ~~grep{/^$/}<>'
最后两个版本不是怎么高效,因为他们需要把整个文件读到内存再筛选。而第一个是逐行处理的。
## 20.打印文件中匹配模式的行数(模仿 grep -c
perl -lne '$a++ if /regex/; END {print $a+0}'
这个行代码基本上和前一个一样,只不过用了更一般的正则表达式/regex/
# 数值计算
## 21.检查一个数字是否是质数
perl -lne '(1x$_) !~ /^1?$|^(11+?)\1+$/ && print "$_ is prime"'
这行代码巧妙使用一个正则表达式判断给予的数字是不是质数。不要想得太复杂了,我引用这个例子主要为了说明其他的技巧性。
首先这个数字通过“1x$_”被转变为他一元表达式。例如,5被转化了“1x5”也就是“11111”。
接着,对这个一元表达式进行用独特的正则表达式进行匹配,如果不匹配这个数字是个质数,否则他就是是个合数。
这个正则表达式由两部分组成:“^1?$”和“^(11+?)\1+$”。
第一个部分匹配“1”和空字符串。很明显空字串和1不是质数因此这个表达式匹配了则表明这个数字不是一个质数。
第二部分检测是否两次或者更多次重复的1的组成了这个数字正则匹配了意味着数字由这样的多个1的重复组成的否则的话它是一个质数。
下面我们以5和6为例子说明第二部分这个正则。
5的一元的表达式为“11111”。“(11+?)”首先会匹配两个1 “11”。后面引用“\1”变成“11”,于是整个正则表达式变成了“^11(11)+$”。这个不会匹配11111于是匹配失败。由于正则部分使用了“+?”它会回溯接着匹配三个1“111”。\1于是变成了“111”这个正则表达式变成了“^111(111)+$”。这个也不会匹配。于是又重复用“1111”和“11111”匹配当然也不会匹配。直到所有模式都尝试一遍都失败因此这个数字是个质数。
数字6的一元表达式为“111111”。“(11+?)”首先匹配两个1 “11”整个正则表达式为“^11(11)+$”。这个会匹配“111111”所以数字不是一个质数。
“-lne”在第一部分和第二部分已经解释过不再赘述。
## 22.打印一行中所有的域的和。
perl -MList::Util=sum -alne 'print sum @F'
这行代码使用了一个域自动切割的命令行选项“-a”并且通过“-MList::Util=sum”引入了“List::Util”模块的“sum”求和函数。List::Util”模块是Perl核心模块不需要担心额外安装的问题。
作为一个自动分割的结果,分割的域保存在“@F”数组中“sum”函数只不过对其求和而已。-Mmodule=arg选项引入了一个模块相当于语句use module qw(arg),这行代码相当于: use List::Util qw(sum);while (<>) { @F = split(' '); print sum @F, "\n";}
## 23.打印所有行所有域的值
perl -MList::Util=sum -alne 'push @S,@F; END { print sum @S }'
这行代码通过push操作把每行分割的@F数组保存到@S数组中去当所有行都输入后perl退出前END{}块被执行调用sum函数对@S的所有成员求和。这个和就是所有行所有域的和。
这个方案不是最好选择。它生成了一个额外的数组@S,最好的方式是是下面的:
perl -MList::Util=sum -alne '$s += sum @F; END { print $s }'
## 24.以随机顺序显示每行的各个域。
perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"'
本行和例22基本上相同只不过本例使用shuffle函数将行域顺序随机打乱并显示出来。
“@{[shuffle @F]}” 结构创建了一个指向“shuffle @F”内容的数组引用,并且通过“@ {…}”将其解引用成其内容。这是一个执行插入到引用中代码的常用技巧。这方法于我们需要把@F顺序打乱,并以空格分割输出是必须的。
另一种方法可以实现同样的功能是通过join把@F的各个元素和空格连接起来,但是代码会更长一点:
perl -MList::Util=shuffle -alne 'print join " ", shuffle @F'
## 25.找到每一行的最小的元素
perl -MList::Util=min -alne 'print min @F'
本行代码使用了“List::Util”模块的“min”函数形式和前面几例都相似。每行通过“-a”选项自动分割并用“min”函数求出最小的元素并打印出来。
## 26.找到所有行中最小的元素。
perl -MList::Util=min -alne '@M = (@M, @F); END { print min @M }'
这行代码是上一例子以及例23的整合。“@M=@M,@F”结构相当于“push @M,@F”,把@F的内容附加到数组@M中
本代码需要把所有数据都存进内存。如果你用它去处理10T大的文件他肯定会挂掉。因此因此更好一点的方法是只把最小等数存进内存最后输出
perl -MList::Util=min -alne '$min = min @F; $rmin = $min unless defined $rmin && $min > $rmin; END { print $rmin }'
代码会找出每一个行最小的一个并存进 $min,接着检查他是不是比当前最小数$rmin还小。最后打印当前最小数保证了在所有的输人中他是最小的一个值。
## 27.找出一行中最大的数
perl -MList::Util=max -alne 'print max @F'
和例25完全相同只不过函数用最大函数“max”。
## 28.找出所有行中最小的一个
perl -MList::Util=max -alne '@M = (@M, @F); END { print max @M }'
和例子26一样也有另一种形式
perl -MList::Util=max -alne '$max = max @F; $rmax = $max unless defined $rmax && $max < $rmax; END { print $rmax }'
## 29.对所有的域的值用其绝对值代替
perl -alne 'print "@{[map { abs } @F]}"'
本行代码,先通过“-a”命令行参数自动分割行的各个域接着调用绝对值函数“abs”通过map对其进行整体操作。最后利用匿名数组方式将各个域连接。@{ ... }解释见例24.
## 30.找到每行域个数
perl -alne 'print scalar @F'
本行代码强制@F到标量上下文这在perl中就是@F的元素个数。因此输出结果就是每行的域个数。
## 31.输出每行,并在每行前面显示域数量。
perl -alne 'print scalar @F, " $_"'
本例和例30基本上一样除了把每行的内容“$_”也打印出来了。提示“-n”选项会导致进行输入循环并把每行的内容存储在$_变量
## 32.计算所有行的域总数
perl -alne '$t += @F; END { print $t}'
在这我们仅仅通过把每行的域的数量累加,并用变量“$t”保存最后输出。结果就是所有域的总数。
## 33.打印出匹配模式的域总数。
perl -alne 'map { /regex/ && $t++ } @F; END { print $t }'
这行代码使用map函数对@F数组的各个元素进行操作。本例中是检查其是否匹配模式/regex/,如果是的话,给变量$t加1。在最后END模块中打印出$t的值即为匹配的域的总数。
还有一种比较好的方法是:
perl -alne '$t += /regex/ for @F; END { print $t }'
每一个@F的元素用/regex/模式检验。如果匹配/regex/会返回值1表示真我们用+=操作符累加并赋值给$t.用这种方式使得匹配的数量会被保存在$t。
最好的方式是利用标量上下文的grep
perl -alne '$t += grep /regex/, @F; END { print $t }'
在标量上下文grep会返回匹配的数量数量也用$t进行累加。
## 34.打印所有匹配模式的行总数
perl -lne '/regex/ && $t++; END { print $t }'
如果当前行输出匹配正则模式,/regex/为true。/regex/ && $t++相当于if ($_ =~ /regex/) { $t++ },如果模式匹配,$t值就会加1.最后END模块输出$t值就是匹配模式的行的数量。
## 35.打印PI的值到n位小数点后。
perl -Mbignum=bpi -le 'print bpi(21)'
bignum库的bpi函数可以计算输出PI常量到所需足够精确地值。本例打印PI到小数点后20位。
bignum库也可以直接输出PI常量这个数值保留39位的精确度
perl -Mbignum=PI -le 'print PI'
## 36.打印E的值到n为小数点后。
perl -Mbignum=bexp -le 'print bexp(1,21)'
Bignum库也可以利用bexp函数来计算和输出e及其n方幂的精确值他的两个参数为幂值n以及精确位。本里打印了e的值1一次方到20位小数点后。
## 37.打印e的平方到31位精确值
perl -Mbignum=bexp -le 'print bexp(2,31)'
和PI一样binum也支持输出常量e值也保持39位的精确度。
perl -Mbignum=e -le 'print e'
## 38.打印Unix 时间值自从1970年一月一日, 00:00:00 UTC后累计秒的值
perl -le 'print time'
内建的函数“time”返回公元以来的秒数。
## 39.打印GMT格林威治时间和本机时间。
perl -le 'print scalar gmtime'
“gmtime”函数是Perl内建的函数。如果使用标量上下文他会打印格林威治时区0时区标准时间。
perl -le 'print scalar localtime'
“localtime”内建函数表现和“gmtime”大致一样不过他显示的计算机本地的时间。
“gmtime”和“localtime”均返回9元素的列表tm结构体包含下述元素
($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切割出localtime的21和0元素
perl -le 'print join ":", (localtime)[2,1,0]'
## 40. 打印昨天的日期
perl -MPOSIX -le '@now = localtime; $now[3] -= 1; print scalar localtime mktime @now'
上例说了localtime返回一个9元素的各种时间项。其中第4项是当前日期数。如果我们对其减去1就得到昨天。“mktime”函数利用修改过的9项目列表构建一个Unix时间戳。最后“scalar localtime”打印了这个新的日期就是昨天。
因为使用到了mktime函数POSIX包是必须的他应该是用于规范化负值。
## 41.打印14个月9天7秒前的日期
perl -MPOSIX -le '@now = localtime; $now[0] -= 7; $now[4] -= 14; $now[7] -= 9; print scalar localtime mktime @now'
本行代码修改了@now列表的第1个第5个以及第8个元素。第1个是秒数第5个是月第8个是日见上面时间9元素表.
接着利用mktime生成把它转化成了Unix时间戳再利用标量上下文的localtime打印日期14个月9天7秒钟前。
## 42.计算阶乘。
perl -MMath::BigInt -le 'print Math::BigInt->new(5)->bfac()'
本行代码使用Math::BigInt模块的bfac函数这个模块内置与Perl核心不必另行安装
Math::BigInt->new(5)结构创建一个新的Math::BigInt对象构造参数为5。接着用bfac方法调用新创建的对象计算5的阶乘。如果计算其他数的阶乘只需要在创建对象时候更换构造参数的值为所希望的值即可。
另一种计算阶乘的方法是直接累乘从1到n的值
perl -le '$f = 1; $f *= $_ for 1..5; print $f'
我们给$f初始化赋值为1.接着做一个从1到5的循环对$f进行各个值的累乘。结果是1*2*3*4*5,这就是5的阶乘。
## 43.计算最大公约数。
perl -MMath::BigInt=bgcd -le 'print bgcd(@list_of_numbers)'
Math::BigInt还内带有其他好多个非常有用的数学函数。这其中之一是bgcd用来计算一列数字的最大公约数。
perl -MMath::BigInt=bgcd -le 'print bgcd(20,60,30)' 当然,你也可以使用欧几里德算法(即辗转相除法)。给予两个数字$n和$m,这行代码找出两者的gcd。结果保存在$m中。 perl -le '$n = 20; $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $m'
## 44.计算最小公倍数。
另外一个函数是lcm计算最小公倍数一下行代码计算35208的最小公倍数。
perl -MMath::BigInt=blcm -le 'print blcm(35,20,8)'
如果你了解一些数字定理你应该能知道gcd和lcm之间是有联系的。给定两个数字$n和$m,他们的lcm等于$n*$m/gcd($n,$m),因此,也就是另一种计算最小公倍数的行代码:
perl -le '$a = $n = 20; $b = $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $a*$b/$m'
## 45.生成5到15之间包括15的10个随机数
perl -le '$n=10; $min=5; $max=15; $, = " "; print map { int(rand($max-$min))+$min } 1..$n'
你能通过改变$n,$min,$max调整此行代码。变量$n表示要生成多少个随机数[$min,$max)表示生成数的访问。
变量$,设为空格,用来格式化输出的域间隔,默认没有设置。所以如果我们不设置它为空格,打印的数字将会连在一起。
## 46.找出列表的所有排列方式并打印出来。
perl -MAlgorithm::Permute -le '$l = [1,2,3,4,5]; $p = Algorithm::Permute->new($l); print @r while @r = $p->next'
这行代码使用面向对象的接口Algorithm::Permute模块来找出排列所有重新组合元素的方法
Algorithm::Permute的构造方法接受一个数组引用参数来排列。本例中元素为1,2,3,4,5。
对象方法的方法next返回下一个排列。循环调用这个方法可以获得所有可能的排列。
我们可以注意到很快就能输出大量的列表。对一个n个元素的列表来说有n!种排列。
另一种方法是使用permute子函数。
perl -MAlgorithm::Permute -le '@l = (1,2,3,4,5);Algorithm::Permute::permute { print "@l" } @l'
## 47.生成幂集。
perl -MList::PowerSet=powerset -le '@l = (1,2,3,4,5); for (@{powerset(@l)}) { print "@$_" }'
本例我使用了CPAN的List::PowerSet模块。我输出powerset函数本函数以一列表做为参数返回一个引用该应用为指向子集列表的引用的列表。
在for循环调用了powerset函数传递给它@l的列表元素。接着解引用返回powerset的值这个值是一个指向子集列表的引用。最后解引用每一个子集@$_并打印它。
对一个n元素的集合他的幂集有2n个子集。
## 48.转换IP地址为无符号整数。
perl -le '$i=3; $u += ($_<<8*$i--) for "127.0.0.1" =~ /(\d+)/g; print $u'
本行代码转换127.0.0.1的IP地址为无符号整数他恰巧是2130706433)。
首先对IP地址做了个\d+全局匹配做一个for循环做全局匹配的迭代使所有数字得到匹配匹配了IP地址的四个部分
接着把匹配的数字加起来保存在$u中其中第一部分位移8*3=24位第二部分位移2*8=16位第三部分8位最后一部分直接加给$u。
但是本行代码没有对IP地址做任何的格式检验你也可以使用一个更复杂一点正则表达式来检测比如/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/g
经过和朋友一起讨论我们想出另外一些方法
perl -le '$ip="127.0.0.1"; $ip =~ s/(\d+)\.?/sprintf("%02x", $1)/ge; print hex($ip)'
这个行代码巧妙地利用127.0.0.1可以被容易的转化为十六进程 7f000001的事实再利用Perl的hex函数转化为十进制
另一种方法是利用unpack
perl -le 'print unpack("N", 127.0.0.1)'
这个行代码大概是能达到最短的一个它利用vstring语法版本字符表达IP地址Vstring格式是指用特定顺序值组成的字符串接着利用网络字节顺序Big-Endian顺序新的格式化字符串被解包为一个数字并把它打印出来
如果有一个IP的字符串不是一个vstring你首先就要用inet_aton函数转化这个字符串为字节的形式
perl -MSocket -le 'print unpack("N", inet_aton("127.0.0.1"))'
这儿inet_aton函数转化字符串127.0.0.1 为字节的形式这恰恰和纯vstring 127.0.0.1是一样的接着unpack它
## 49.转化一个无符号整数为IP地址。
perl -MSocket -le 'print inet_ntoa(pack("N", 2130706433))'
本例中整数2130706433首先被pack为一个Big-Endian数字并且利用inet_ntoa函数转化这个数字为IP地址
另外一种方法是用位移和逐字节打印的方式
perl -le '$ip = 2130706433; print join ".", map { (($ip>>8*($_))&0xFF) } reverse 0..3'
随便说一下join “.”可以被特殊变量$,代替,它专门用来做为打印语句的默认分割符:
perl -le '$ip = 2130706433; $, = "."; print map { (($ip>>8*($_))&0xFF) } reverse 0..3'
# 字符和数组生成
## 50.生成和打印首字母表
perl -le 'print a..z'
本行代码打印所有从a到z的字母“abcdefghijklmnopqrstuvwxyz”。本例中应用了范围操作符“...”生成字母。当范围操作符用在字符列表上下文的时候会从左边使用魔力自增算法向前递增一个字符直到范围右边的字符。于是这行代码通过a..z的范围依次递增从a到z的整个字母表。
由于使用了裸字符 a和z如果你开启strict模式这行代码将会报错。从语法角度更正确的版本是
perl -le 'print ("a".."z")'
记住访问操作符..生成是一个列表值。如果你愿意,你可以打印出他们,之间通过“$,”指定的分割符间隔开来,比如:
perl -le '$, = ","; print ("a".."z")'
从语法上讲更好的方法是采用join方法来将列表的元素之间分割。
perl -le 'print join ",", ("a".."z")'
上面通过join函数吧a..z列表用逗号隔开并打印出了。
## 51.生成并打印从“a”到“zz”的所有字串。
perl -le 'print ("a".."zz")'
本例我们又实用了范围操作符“..”。这次不是终止于“z”而是更前进一个字符生成“aa”接着继续往前生成“ab”“ac”,…,达到 “az”。在这个点上往前到了“ba”接着继续,直到达到“zz”。
同样地你可以生成从“aa”到“zz”的左右字符
perl -le 'print "aa".."zz"'
## 52.生成一个16进制查询表。
@hex = (0..9, "a".."f")
此处,数组@hex会保存0, 1, 2, 3, 4, 5, 6, 7, 8, 9以及字母a,b,c,d,e,f等值。
perl -le '$num = 255; @hex = (0..9, "a".."f"); while ($num) { $s = $hex[($num%16)&15].$s; $num = int $num/16 } print $s'
很明显转化一个数字为16进制的更简单的方法是用%x格式符的printf函数或者sprintf函数上例演示了使用我们用访问操作符生成的16进制查询表
perl -le '$hex = sprintf("%x", 255); print $hex'
## 53.把16进制数字转换回10进制使用hex函数
perl -le '$num = "ff"; print hex $num'
hex函数输入一个10进制字符可以是“0x”开头或者不是并把它转化为10进制。
## 53.生成一个8字符的随机密码。
perl -le 'print map { ("a".."z")[rand 26] } 1..8'
此处map函数执行("a".."z")[rand 26]代码8次由于要在虚拟的范围1..8循环。每一次循环代码会从字母表中随机选出一个字母。当map执行完循环返回生成的字符列表并连接在一起打印出来。
如果和你还希望在密码中包含数字可以把0..9增加到字符列表并且选择范围从26变为36因为有36个不同的字符以供选择
perl -le 'print map { ("a".."z", 0..9)[rand 36] } 1..8'
如果你需要更长的密码可以把1..8变为1..20生成一个20个字符串的长密码。
## 54.生成一个特定长度的字串。
perl -le 'print "a"x50'
操作符“X”是重复操作符。本行代码生成一个50个字符“a”组成的自串并打印出来。
如果重复操作符用于列表上下文,他将生成由所给元素重复次数的一个列表(而不是标量)。
perl -le '@list = (1,2)x20; print "@list"'
本行代码生成一个20次重复12的列表看起来像1212,…))。
## 55.有一个字符串生成一个数组。
@months = split ' ', "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
此处@months会保存包含来自于字符串中的月份名称的值。由于每一个月份名又空格隔开split函数分割开他们并保存在@months数组中。于是$months[0]包含”Jan”, $months[1]包含”Feb” $months[11]包含”Dec”。
另外一种方法是使用qw//操作符:
@months = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/
qw//操作符接收一个空格分割的字符,生成一个数组,每一个单词为数组中的一个元素。
## 56.由一个数组生成一个字符串。
@stuff = ("hello", 0..9, "world"); $string = join '-', @stuff
此处,数组@stuff的所有值缩为一个字符串$string之间用连字符分隔开。把一个数组转化为字符串使用join函数join函数接收一个分隔符和一个列表把列表中的所有元素连接为一个字符串之间用指定分隔符隔开。
## 57.找出字串中字符的区位码
perl -le 'print join ", ", map { ord } split //, "hello world"'
本行代码引入字符串“hello world”利用split//”hello world”把其分割为字符列表接着用ord函数map每一个字符为8为区位码数字比如ASCII或者 EBCDIC。最后所有数字join连接在一起用逗号隔开并打印出来。
另一种实现同样功能的方法是使用unpack函数指定C*作为unpack模板C意思是无符号字符*指所有字符):
perl -le 'print join ", ", unpack("C*", "hello world")'
## 58.转换ASCII码值列表为一个字串。
perl -le '@ascii = (99, 111, 100, 105, 110, 103); print pack("C*", @ascii)'
和上例我们看克unpack一个字串为值列表一样我们也可以反过来他们pack回一个字串。
另一种方法是利用chr函数以码值为参数返回相对应的字符
perl -le '@ascii = (99, 111, 100, 105, 110, 103); print map { chr } @ascii'
和上面$55例相似利用chr函数的map每一个@ascii的值为字符
## 59.生成1到100所有奇数的数组。
perl -le '@odd = grep {$_ % 2 == 1} 1..100; print "@odd"'
本行代码生成从1到99的奇数组成的数组13579,…,99。我们使用grep筛选列表1到100的各个元素筛选代码用的是$_ % 2 ==。只有筛选代码评判为true的元素会返回。在本例中测试是否除2余数为1如果是这个数字为奇数他会加到@odd数组中
另外一种方法基于奇数具有低字节位为1的事实并以此为测试
perl -le '@odd = grep { $_ & 1 } 1..100; print "@odd"'
表达式$_ & 1 分离出低字节位出来并且grep筛选低字节位为1奇数
关于字节见explanation of bit-hacks。
## 60.生成1到100所有偶数的数组
perl -le '@even = grep {$_ % 2 == 0} 1..100; print "@even"'
本行代码和上例基本上一样除了grep test条件是偶数能被2整除
## 61.计算字串的长度。
perl -le 'print length "one-liners are great"'
作为结束length子程序返回字符串的长度。
## 62.计算数组元素个数
perl -le '@array = ("a".."z"); print scalar @array'
在标量上下文评估一个数组(或者哈希)的值会返回数组元素的数量。
另外一种计算方法是数组最后一个元素下表值加1
perl -le '@array = ("a".."z"); print $#array + 1'
此处,$#array返回数组@array的最后一个元素的下标值由于下标由0开始所以最后一个元素的下标比数组个数小1所以通过加1就可以获数组的元素个数。
# 文本转化和替换
## 63.ROT13化字串
'y/A-Za-z/N-ZA-Mn-za-m/'
本行代码使用y操作符或者页脚tr操作符做ROT13转化。y和tr操作做会对搜索列表的字符以替换列表对应位置的字符逐字替换。
本行代码A-Aa-z生成如下的字符列表
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
而N-ZA-Mn-za-m 生成的列表为:
NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm
如果你观察的比较仔细的话就可以看出来第二行列表实际上和第一个列表偏移了13个字符。于是用y操作符把第一个列表所有字符用第二列表替换你正好就执行了ROT13操作。
如果你要对整个文件做ROT13
perl -lpe 'y/A-Za-z/N-ZA-Mn-za-m/' file
-p参数会把文件的每行都放到$_变量中y做ROT13操作在用-p输出$_out.-l参数附加一个换行符。
注意ROT13两次会得到同样的字符。例如ROT14ROT13string==string。
## 64.Base64加密
perl -MMIME::Base64 -e 'print encode_base64("string")'
本行代码使用MIME::Base64模块Perl核心自带无需额外安装。这个模块的encode_base64函数接受字串为参数对其做Base64加密。
如果对整个文件做加密,使用下面代码:
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)' file
此处-0777参数和-n用在一起导致Perl加载整个文件到$_变量。接着对其中base64加密就像上面的字串的例子一样的。
如果我们不加载整个文件,而是通过逐行加密的话结果会混乱。
## 65.base64解码
perl -MMIME::Base64 -le 'print decode_base64("base64string")'
MIME::Base64模块也有解码函数decode_base64.decode_base64函数接收base64加密的字串对其进行解码。
对这个文件解码也可以很简单:
perl -MMIME::Base64 -ne 'print decode_base64($_)' file
此处不需要加载整个文件到$_因为base64加密的文件每一行都恰好为76个字符很好解码。
## 66.对字串URL转码
perl -MURI::Escape -le 'print uri_escape($string)'
你需要安装另外URI::Escape模块这个模块输出两个函数uri_escape 和uri_unescape第一做url转码另一个做URL解码。
## 67.对字串URL解码
perl -MURI::Escape -le 'print uri_unescape($string)'
本行代码使用uri_unescape函数做URL解码。
## 68.对字串做HTML转码
perl -MHTML::Entities -le 'print encode_entities($string)'
本行代码使用HTML::Entities 模块的encode_entities函数。这个函数用来转码HTML实体。例如< >转化为&lt;&gt;
## 69.对字串做HTML解码
perl -MHTML::Entities -le 'print decode_entities($string)'
本行代码使用HTML::Entities 模块的deencode_entities函数。
## 70.所有文本大写
perl -nle 'print uc'
本行代码使用uc函数它默认对$_变量进行操作并且返回其大写形式。
另外一种方法是用-p命令行选项使其自动打印$_变量并且对其就地处理
perl -ple '$_=uc'
也可通过使用\U转义序列的方法达到同样的功能
perl -nle 'print "\U$_"'
这会让其后所有的字符(或者直到出现\E为止都大写。
## 71.所有文本小写
perl -nle 'print lc'
本行代码和前一例完全相似不过此处使用的lc函数爸$_的内容转化为小写。
或者,使用转义序列\L做字符改写
perl -nle 'print "\L$_"'
此处\L会让其后所有的字符或者直到出现\E为止都小写。
## 72.对每行的首字母大写
perl -nle 'print ucfirst lc'
本行代码使用lc函数使输入的转化为小写并利用ucfirst函数似的第一个字字符大写。
也能用转义码文本转换的方法实现同样的功能:
perl -nle 'print "\u\L$_"'
第一个\L使整行都小写\u使得第一个字符大写。
## 73.大小写转化
perl -ple 'y/A-Za-z/a-zA-Z/'
本行代码把大写字母A-Z转化为小写a-z而把小写字母大写从而实现了大小写转化。
## 74.每行进行驼峰大小写转化
perl -ple 's/(\w+)/\u$1/g'
这是一个差劲的驼峰表示法行代码。它会把每个单词的首字母都大写。对诸如“frieds car”会转化有误会把它转化为“ Friend'S Car”。
一个改进是:
s/(?<!['])(\w+)/\u\1/g
它会检查是否一个单词前的字符是不是单引号“’”。但是在某些更特殊的情况下它可能仍然会失败。
## 75.去除每行前面的空白(空格tab等)
perl -ple 's/^[ \t]+//'
本行代码删除了所有的每行前面的所有空格。他使用替换操作符s。对一个模式s/REGEX/REPLACE/会把匹配的REGEX用REPLACE字串代替。在本例中REGEX是^[ \t]+,意思是在字符串的开始匹配一个或者更多个空格或者tabREPLACE为空意思是把匹配部分替换为空字串删除
正则类[ \t]实际上可以用\s+来替换,\s+匹配任何的空格包括tabs和空格
perl -ple 's/^\s+//'
## 76.删除每行的每行末的空白
perl -ple 's/[ \t]+$//'
本行代码删除了每行末所有空白字符。
## 77.删除每行开始和结束的空白
perl -ple 's/^[ \t]+|[ \t]+$//g'
本行代码结合了前面两例。注意到,它给操作符指定了全局的/g标记。这是必须的由于我们期望删掉开始和结束的空白。如果我们不指定他将只能删除开始的假设存在的话而不会删除最后的。
## 77.转化Unix换行符为DOS/Windows换行符
perl -pe 's|\n|\r\n|'
本行代码替换Unix换行符\n LF为windows换行符\r\n CRLF。记住s操作符可以使用任何字符作为分隔符。本行代码用竖线作为分隔符以增加代码的可读性。
## 78.转化DOS/Windows换行符为UNIX换行符。
perl -pe 's|\r\n|\n|'
本行代码做了前一例的逆操作。操作window 换行发CRLF,并把它转化为换行符LF。
## 79.转化Unix换行符为Mac换行符。
perl -pe 's|\n|\r|'
苹果使用\r CR作为换行符。本行代码转化Unix的LF为苹果的CR。
## 80.替换行中foo为bar查找替换
perl -pe 's/foo/bar/'
本行代码使用s/REGEX/REPLACE/对每行中foo的替换为bar
替换行中所有foo为bar则需要增加/g标记
perl -pe 's/foo/bar/g'
## 81.替换匹配“baz”的行中的foo为bar
perl -pe '/baz/ && s/foo/bar/'
本行代码等价于:
while (defined($line = <>)) {
if ($line =~ /baz/) {
$line =~ s/foo/bar/
}
}
他先检查每行是否匹配“baz”如果匹配则替换行中foo为bar。
## 82.逆序输出段落
perl -00 -e 'print reverse <>' file
本行代码使用了-00参数这在例7讨论过用来开启大段落模式意味着Perl将通过段落的方式读取文本而不是默认的按行的方式。接着使用<>操作符使得Perl用指定的文件或者标准输入获得输入。本处我们指定file作为参数所以Perl读取file的文本段落由于-00。当Perl读完文件它将所有段落作为一个列表返回并且调用reverse函数颠倒段列表的顺序。最后打印出反序的段落列表。
## 83.逆序输出所有行
perl -lne 'print scalar reverse $_'
本行代码在标量上下文下reverse操作。在上一例中对整个列表在在列表上下文中执行reverse结果是对列表元素顺序进行反序。对标量变量比如包含整行的$_你要实现同样的功能必须在标量上下文进行reverse操作。否则它执行的将会对一个元素的列表rreverse得到结果还会是他自己作为操作后打印出反序的行。你可以去掉代码中$_执行结果一样的。因为reverse默认的变量就是$_。换句话说行代码可以写为
perl -lne 'print scalar reverse'
或者你可以用-p替换-n修改$_变量使其值为倒序
perl -lpe '$_ = reverse $_'
也可以简写为:
perl -lpe '$_ = reverse'
要说明的是在Perl的操作符如果没有指定参数的话大多数情况下默认都是对$_操作。
## 84.以逆序方式打印列
perl -alne 'print "@{[reverse @F]}"'
本行代码对文件中的列逆序转化。-a命令行参数用空格为分隔符切割每一行为列并存在@F数组中。接着对其逆序并且打印。
本行代码在以有过类似的例子,@{[...]}结果,为了解释其作用,我们假设在其中插入了双引号,给予下面的输入文件:
one two three four
five six seven eight
其输出为:
four three two one
eight seven six five
如果输入的行的各列直接不是空格而是其他的分隔符,则我们必须用-F命令行设置为特殊的字符如果给输入文件为
```
one:two:three:four
five:six:seven:eight
```
这我们增加-F:参数,给行代码:
perl -F: -alne 'print "@{[reverse @F]}"'
它的输出为:
four three two one eight
seven six five
注意到,在输出的时候没有带:字符,为了使其带上,我们需要对本行修改,设置$”变量为“:”,如下:
perl -F: -alne '$" = ":"; print "@{[reverse @F]}"'
他会输出我们所期望的结果:
```
four:three:two:one
eight:seven:six:five
```
变量$”用于改变在双引号环境下对数组进行变量内插打印时元素间默认的分割字符(空格)。
# 有选择的打印或者删除行
## 85.打印文件的首行(模仿head -1)
perl -ne 'print; exit'
本行代码迄今为止最简单一个。此处由于使用-n参数Perl读了第一行到$_接着调用print语句输出 $_变量的内容。接着推出。就这样第一行就被打印出来了而这恰好就是我们需要的。
## 86.打印文件开始的10行模仿head -10
perl -ne 'print if $. <= 10'
本行代码是使用了$.特殊变量。这个变量带包“当前行序号”。每次Perl读下一行的时候$.的值为自动加一。因此本行代码非常好理解当行序号等于或者小于10的时候打印出行内容。
本行代码也可以用不带if语句的方法
perl -ne '$. <= 10 && print'
此处,只有当$.<=10布尔表达式为true时执行打印语句而只有行号小于或者等于10时候表达式为真。
## 87.打印最后一行模仿tail -1
perl -ne '$last = $_; END { print $last }'
打印最后一行的代码需要一点技巧,因为你一直需要把前一行保存在内存里。本行代码总我们一直把当前行保存到$last变量。当Perl代码结束时候总会执行END块的。现在当读完最后一行执行END块退出的时候我们打印出$last变量这就是最后一行的内容。
另外一种同样同能代码是,
perl -ne 'print if eof'
本行代码使用eof函数这个函数在下一次读到文件的末尾时候返回1。由于读到文件最后一行的时候下一行读文件末尾返回eof为真所以本代码作出了我们期望所做的事情。
## 88.打印文件的最后10行模拟tail -10
perl -ne 'push @a, $_; @a = @a[@a-10..$#a]; END { print @a }'
这个有点复杂。此处我们把每一行都put到数组@a中,然后用他的数组切片代替他。我们做 @a = @a[@a-10..$#a],这表示用@a的最后10个元素取代他自己@a-10在标量上下文被估值他返回值为数组元素个数减10。#$a是@a数组的最后一个下标@a[@a-10..#$a]取得了数组的最后10个元素的下标所以@a保存了最后10个元素
举例子假设数组@a保存("line1", "line2", "line3", "line4")。我们想要打印文件最后4行。当我们读到滴5行时候数组变成了("line1", "line2", "line3", "line4", "line5")。这时,由于@a在标量环境下估值为5@a-4值为1。#$a值为4。数组切片@a[@a-4..$#a]则为@a[1..4],这去掉了数组中的第一个元素。当作为替换后@a就变成了("line2", "line3", "line4", "line5")。
## 89.仅打印匹配模式的行。
perl -ne '/regex/ && print'
此处/regex/是$_ =~ /regex/的缩写。由于-n操作符会把每一行都放到$_变量中所有匹配模式的行/regex/会返回true就会打印出行内容。
## 90.仅打印模式不匹配的行。
perl -ne '!/regex/ && print'
本行和尚一行基本上一样,但是模式表达被取反了。于是会打印所有不匹配的行。
## 91.打印匹配行之前的一行。
perl -ne '/regex/ && $last && print $last; $last = $_'
本行代码中我们把每一行的内容保存到$last变量中。当行匹配模式的时候$last中是其上一行的内容于是打印$last接着把当前行的内容复赋值给$last变量。
## 92.打印模式匹配行之后的一行。
perl -ne 'if ($p) { print; $p = 0 } $p++ if /regex/'
此处如果行匹配模式的话我们设置变量$p的值。用它指示下一行将会被打印。当下一行被读到$p被设置于是这行被打印并且$p被又被设置为0重置其状态。
## 93.打印任意顺序匹配AAA和BBB的行。
perl -ne '/AAA/ && /BBB/ && print'
本行代码基本上和此前例86一样只不过这儿测试的模式变成了两个如果一个行匹配了两个模式他就会被打印出来。
## 94.打印不匹配AAA和BBB模式的行。
perl -ne '!/AAA/ && !/BBB/ && print'
本行代码基本上和此前例87一样此处测试行是否不匹配任意顺序两个模式。如果不匹配/AAA/ 并且不匹配/BBB/,我们打印出这行。
## 95.打印匹配模式AAA接着模式BBB再接着模式CCC的行。
perl -ne '/AAA.*BBB.*CCC/ && print'
此处简单的将三个模式AAABBB,CCC用“.*”连了起来表示匹配这所有的或者不匹配。如果AAA跟着BBB又跟着CCC模式这行就会被打印。他也会匹配AAABBBCCC这样的。
## 96.打印所有80字符或者大于80字符的行
perl -ne 'print if length >= 80'
本行代码打印所有大于或者等于80字符的行。Perl中你时常可以省略掉函数调用时候的括弧。此处我们省略了长度函数的括弧。实际上length,length() 和length($_)都是一样的。
## 97.打印小于80字符的行。
perl -ne 'print if length < 80'
这和前一行代码相反检测行长度是否小于80字符
## 98.仅带打印13行
perl -ne '$. == 13 && print && exit'
和此前例13介绍的一样$.变量表示当前行数所以如果$.等于13我们就打印了13行并退出
## 99.打印除27行外的所有行。
perl -ne '$. != 27 && print'
和上一行代码类似我们检测当前行是否为27行如果不是我们打印它否则跳过
另一种实现同样功能的方式是颠倒print和$.!=27,使用if语句
perl -ne 'print if $. != 27'
## 100.仅打印1319和67行。
perl -ne 'print if $. == 13 || $. == 19 || $. == 67'
如果你是用Perl 5.10或者更新版本你可以使用~~~智能匹配符
perl -ne 'print if int($.) ~~ (13, 19, 67)'
智能匹配符“~~仅在Perl 5.10才推出你可以用它做所有类型的智能模式匹配例如检查是否两个数组是一样的是否一个数组包含一个元素以及其他很多用法见perldoc perlsyn)。在本行代码中我们使用 int($.~~(13,19,67) 用来检测是否数值$.在列表131967这基本上是对代码 grep {$_==int($.)} (13,19,67)缩写如果检测成功行就会被打印
## 101.打印匹配两个模式之间的所有行(包括匹配模式的行)
perl -ne 'print if /regex1/../regex2/'
本行代码使用触发操作符当某行匹配regex1他变为ture当其后另一行匹配了regex2时候变为false因此这个行代码就会打印出匹配了两个模式之间的所有行
## 102.打印17到30之间所有行
perl -ne 'print if $. >= 17 && $. <= 30'
本行代码非常容易理解。$.变量表示当前行号于是它检测是否当前行号大于等于17并且小于等于30。
另一种写法是,
perl -ne 'print if int($.) ~~ (17..30)'
这行代码使用Perl5.10(或者更新版本)的智能匹配操作符~~。这主要是说,当前行号在列表(17, 18, 19, ..., 30)。如果是,智能模式符匹配成功,行就会被打印。
在老Perl版本你可以用下面的代码代替
perl -ne 'print if grep { $_ == $. } 17..30'
这主要是用grep过程探测是否当前行号在列表(17, 18, 19, ..., 30)。如果是返回列表中的一个元素而有一个元素的列表表示为真所以行就会被打印。否则的话返回一个空列表代表为false就不会得到打印。
## 103.打印最长的行。
perl -ne '$l = $_ if length($_) > length($l); END { print $l }'
本行代码保存目前看得见最长的行到$l变量中。一旦当前行$_超过了迄今最长的行就替换它。最后退出之前在END块执行打印出最长的行$l。
## 104.打印最短的行。
perl -ne '$s = $_ if $. == 1; $s = $_ if length($_) < length($s); END { print $s }'
本行代码和上一例正好相反但是我们要找到最短的并且由于$s在首行没有定义我们必须显式的指定其为第一行不然后面的小于判断都会失败得不到想要的结果
## 105.打印包含数字的所有行
perl -ne 'print if /\d/'
本行代码使用正则表达式\d,这表示匹配一个数字检查是否包含如果包含检测成功打印出行
## 106.发现仅仅包含一个数字的行。
perl -ne 'print if /^\d+$/'
本行代码和一例相似不过匹配的不是一行中有个数字而是锚定行的开始和结束正则表达式^\d+$表示匹配在行首和行尾之间的一个或者多个数字
## 107.打印仅仅包含字母的行。
perl -ne 'print if /^[[:alpha:]]+$/'
本行代码检测是否行中只含有字母如果是打印出行此处[[:alpha:]]表示匹配所有的字母你也可以写为[a-zA-Z]。
## 108.隔行打印。
perl -ne 'print if $. % 2'
本行代码打印第一七等等行因为$. % 2当当然行号为奇数时候返回真当前行为偶数时候返回false
## 109.从第二行开始隔行打印
perl -ne 'print if $. % 2 == 0'
本行代码和上一例非常相似但是打印的不是1357而是2468等偶数行这由于当行号为偶数时候$. % 2 == 0为真
## 110.打印所有重复的行。
perl -ne 'print if ++$a{$_} == 2'
本行代码用哈希%a保存了目前为止的所有行并且计算这些行出现的次数如果某行出现2次他就会被打印出因为此时++$a{$_} == 2为真如果此行计数多余两次他不会做任何操作因为此行已经超过2行打印检测为false
## 111.不重复的打印所有行
perl -ne 'print unless $a{$_}++'
此处只有行的哈希值$a{$_}为0时候才会打印每一次Perl读进一行都会将这个值增加1所以这使得只有之前都没有出现过的行被打印
# 正则表达式
## 112.匹配貌似邮件的正则表达式
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
这个正则表达式不能保证匹配都是合法的ip地址匹配成功只不过貌似ip地址的字串比如它可以匹配一个合法的ip地址81.198.240.140同时也能匹配一个非法ip地址比如923.844.1.999
下面解释他是如何工作的正则开始的^符号是一个锚字符用来匹配一个字串的开始接着\d{1,3}匹配一个两个或者三个连续数字。“\.匹配一个点最后的$也是一个锚字符匹配字段的结尾使用^$锚字符非常有必要否则的话诸如foo213.3.1.2bar一样的字串也会被匹配上
这个正则可以简单的把开始部分\d{1,3}\.重复三次的到同样功能的表达式
/^(\d{1,3}\.){3}\d{1,3}$/
## 113.测试数字是否在0-255的范围内
/^([0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/
让我来看这个正则是如何工作的此范围内的数可以为一个两个或者三个数字如果是一个数字我们可以让它为任何[0-9]。如果是两个数字我们也允许它为任何[0-9][0-9]的组合然而如果它是三个数字的则他必须为100多或者200多的数如果为100多的可以用1[0-9][0-9]匹配它如果是200多的数字如果是小于250的可以用2[0-4][0-9]匹配或者250-255的用25[0-5]匹配
## 114.匹配IP地址
my $ip_part = qr|([0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|;
if ($ip =~ /^($ip_part\.){3}$ip_part$/) {
say "valid ip";
}
本正则组合了前面的两个它用my $ip_part=qr/…/操作把正则表达式组合到一起,并且存到$ip_part变量中。接着用这个变量去匹配IP地址的四个部分。
## 115.检查字符串是否貌似email地址。
/.+@.+\..+/
这个正则确保字符串看起来像一个邮件地址注意到这说的是"看起来像"。它不能保证确实是一个邮件地址他的匹配过程是首先匹配一些字符后跟个@符号接着匹配任意字符然后到发现一个点接着匹配更多个字符如果这个匹配成功那么这个字符串至少看起来就像个个邮件地址他有@和.。
例如,admin@ijz.me可以匹配但是admin@ijzme不会匹配因为他匹配不到点\.这是必须的
其实更可靠检测合法邮件地址的方法是是用Email::Valid 模块
use Email::Valid;
print (Email::Valid->address('john@example.com') ? 'valid email' : 'invalid email');
## 116.检测字符串为十进制数。
检测字串是否是数字非常难。我基于正则并且在Perl Cookbook对其做了解释。
/^\d+$/
这个正则匹配一个或者更多的数字\d为了开始符^和结尾符$。但是它不能匹配诸如+3和-3这样的数字。让我们调整一下匹配他们
/^[+-]?\d+$/
此处[+-]?意思是在数字前匹配一个可选的正号或者负号。这个可以匹配+3或者-3但是还不能匹配-0.3。让我们加上它:
/^[+-]?\d+\.?\d*$/
我们在前一个正则的基础上加了\.?\d*,这匹配了一个可选的.在0或者其他数字后面。现在这个正则可以匹配诸如-0.3或者0.3的数字了。
更好的方式是是使用Regexp::Common模块它提供了各种非常有用的正则表达式。比如匹配一个整数你可以用$RE{num}{int}。
那么怎么匹配一个正16进制数字呢见下式
/^0x[0-9a-f]+$/i
这可以匹配十六进制前缀0x紧跟着数字。模式结尾的/i标志确保匹配是不区分大小写的。例如0x57af匹配0X5Fa也匹配但是97匹配不了由于他是一个十进制数。
当然最后是用模块$RE{num}{hex},它会支持负数,小数和逗点的数字组。
那么八进制呢?见下:
/^0[0-7]+$/
8进制数前缀是0其后紧跟八进制数字0-7。例如013匹配但是09不会匹配因为9不是一个合法的八进制数。
更好的方式是使用$RE{num}{oct},好处同上。
最后2进制
/^[01]+$/
二进制只包含0和1.例如0101101匹配但是210101不能因为2不是合法的二进制数字。
更好的方式是使用$RE{num}{bin}。
## 117.检测一个单词在字符串总出现了两次。
/(word).*\1/
这个正则匹配了word紧跟着任意字符随后是同样的word。此处word捕捉了word 分组1并用\1引用了分组1的内容因此这个和写作/word.*word/的模式一样。例如“silly things are silly”会匹配/(silly).*\1/但是“silly things are boring”不会匹配因为后面这个字串中silly没有重复。
## 118.给字串中的所有数字加1.
$str =~ s/(\d+)/$1+1/ge
此处我们使用替换操作符s///。他匹配所有的数字(\d+,把他们捕捉到组1接着把他们的值替换其值加1后的值。g标志确保它会知道字串中的所有的数字e标志使得$1+1作为一个Perl表达式执行。
例如“this 1234 is awesome 444”会被替换为“this 1235 is awesome 445”。
## 119. 提取HTTP头中用户客户端字串
/^User-Agent: (.+)$/
HTTP头以键值对的格式表示的。操作这些字串很简单你只要叫正则机保存值部分在$1组变量。
例如如果HTTP头包含
Host: localhost:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_0_0; en-US)
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
.
那么正则表示式将会提取Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_0_0; en-US)字串
## 120.匹配可打印字符。
/[ -~]/
这个一个非常巧妙的表达式。为了理解他看下ascii的说明。你可以发现空格符起始于0x20~符号是0x7e。所有的空格和~之间的字符的都是可以打印字符。这个正则表达式恰好匹配了这些。[ -~]定义了一个从空格到~的字符范围。这一直是我最喜欢的正则表达式。
/[^ -~]/
这个匹配恰好和[ -~]相反。
## 121.匹配两个HTML签之间的文本
m|([^<]*)|
这个正则表达式匹配了所有... 签之间的内容。此处技巧是([^<]*),它匹配会匹配尽可能多的字符知道发现一个<字符他会开始另一个标签
另外你也可以写成
m|(.*?)|
但是这个有点不一样了例如如果HTML内容是 hello 则第一个正则不能匹配但是第二个正则表达会匹配到hello由于(.*?)匹配尽可能少的内容知道发现这恰好是hello
但是我们一般不建议用正则表达式去匹配和处理HTML使用诸如HTML::TreeBuilder 模块去做这项工作是更明智的选择
## 122.替换所有的签为
$html =~ s|<(/)?b>|<$1strong>|g
此处我假设HTML内容保存在变量$html中。接着<(/)?b>匹配了起始和结束的签,捕捉可选的可选签在组$1接着用 或者 替换匹配的签这取决于匹配的是一个起始或者结束tag签。
## 123.提取正则表达式中所有匹配的部分。
my @matches = $text =~ /regex/g;
此处正则表达式在list上下文中使得他返回所有匹配。匹配的内容被放进@matches数组中
例如,下面的正则表达式提取了一个字符串中所有的数字。
my $t = "10 hello 25 moo 31 foo";
my @nums = $text =~ /\d+/g;
@nums 现在包含了(10, 25, 30)。