perl One-Liners | perl命令行学习3 -a和
perl命令行 -a参数 -F参数
【上集回顾】
上次说到了-p
与-n
参数,其实再加上之前学的-e
参数已经可以做很多事情了,但是为了方便,Perl还有这样一对搭档组合的参数,就是-a
和-F
【参数解释】
-a : 将读入的
$_
进行分割,保存到@F
列表之中,类似于split /分隔符/ , $_;
而这个分隔符是由-F
参数指定的,其实这个功能与awk工具相似
-F : 在添加
-a
参数时候,指定分隔符(可以是正则表达式),如果不加好像是由空格作为分隔符,一般对其进行设置
实例说明
为了更加清晰的说明,还是举例子吧,打开终端或者git for windows
输入
# 例如我需要把来自管道的数据按照空格分隔成一个一个单元,存到列表里面
echo "qwe asd zxc" | perl -n -a -F"\s+"-e '
$" = "\n";
foreach my $item (@F){
print "$item\n";
}
'
--------------------------------------
# 输出
qwe
asd
zxc
这次的例子要比之前的例子复杂一点,我来一一说明
-
echo
在屏幕上打印出qwe asd zxc这个字符串,这字符串中间由空格分成三个部分,分别是qwe、asd、zxc -
echo
的输出进入管道 | - 被
perl
逐行读取(因为只有一行,所以直接读完了) - 读取的字符串赋值给
$_
-
$_
被分割为三份(应-a
的要求,根据-F
(注意双引号是贴着-F参数的,中间没有空格隔开)的指定的\s+(意思是按照一个或者多个空格或者制表符分隔,这里也可以改为" +"),将$_
分割为三份)
qwe asd zxc
^ ^
| |
空格
# 根据空格来划分 \s+ 表示如果有多个空格相连也一并视为一个整体
# 切割之后,空格都消失
/ /
qwe/asd/zxc 成为 @F中的元素 ('qwe','asd','zxc')
/ /
- 分隔的三份按照顺序存在
@F
列表中 - 遍历
@F
列表,将其中的内容打印出来
其实你可能会说,用之前之前学的参数就够了啊!比如
echo "qwe asd zxc" | perl -n -e '
my @F = split /\s+/,$_;
$" = "\n";
print "@F\n";
'
那为什么要这样做呢?其实在平常的文本中比没有感觉到,在linux或者mac系统下面,有很多信息就是以文本的形式给出来的,而且中间一般都是用空格或者制表符分隔的,就比如使用df命令查看磁盘使用情况
df
------------------------------------------
# 输出
Filesystem 1K-blocks Used Available Use% Mounted on
C:/Program Files/Git 104857596 63822260 41035336 61% /
D: 318168060 313664144 125465657 40% /d
E: 41942012 21699268 20242744 52% /e
可以看到很鲜明的由空格或者制表符分隔的信息形式。
问题来了,利用这两个参数,我们可以试着做一下事情
问题1:我需要将所有的盘符提取并打印出来?
来试一下
df | perl -n -a -F"\s+" -e '
print $F[0],"\n";
'
------------------------------------------
# 输出
Filesystem
C:/Program
D:
E:
可是标题行不是我想要的,怎么除去呢?
有多种方法
- 使用特殊变量
$.
$.
意为当前读取的行数
df | perl -n -a -F"\s+" -e '
# 第一行就是标题行了,直接跳过它
if($. == 1){
next;
}else{
print $F[0],"\n";
}
'
------------------------------------------
# 输出
C:/Program
D:
E:
- 借助Linux命令
# awk中NR为内置变量,与上面的perl中的 $. 变量意义相同,就是当前读取的行数
df | perl -n -a -F"\s+" -e '
print $F[0],"\n";
' | awk 'NR>1{print $0}'
------------------------------------------
# 输出
C:/Program
D:
E:
注意:你发现在现实盘符的时候C:/Program Files/Git
显示的是不完整的,只显示了C:/Program
,也就是它被分隔了!!这里要说明一下,文件夹是可以使用空格的(特别是像windows下面的系统文件夹C:/Program Files
,的确是很烦人),这个时候使用空格分隔则要小心,一般在linux和mac下面碰不到这种情况。这里为了演示更加方便,然后便于初次的讲解,我把C盘排除掉。但是要是的确有需要加入C盘来进行处理也是可以进行的,只是有点复杂,这里我不叙述,在文章末尾我进行一下探讨。
问题2:我要计算D盘和E盘总共已经使用的磁盘的内存(排除了C盘,原因见上述说明)
# 与上面一样,还是将df命令的结果读取进来,然后分隔成各个元素存到@F中去
df | perl -n -a -F"\s+" -e '
BEGIN{
$total = 0;
}
chomp;
# 排除C盘
if(m/^C:/){
next;
}
if($. == 1){
next;
}else{
$total = $total + $F[2];
}
END{
print "total use : $total\n";
}
'
----------------------------------------------------
# 输出
total use : 335363412
上面用到了两个特殊的代码块BEGIN{}
和END{}
这两个代码块在perl的单行程序中会经常用到
说明一下它们两个的作用
# 单行程序中的结构 # 流程解释
_________________________________________________________
BEGIN{ | +++++++++ 读取文件之前
代码1; | + 代码1 + 就运行代码1
} | +++++++++ 只运行一次
|
| ---> ++++
| ---> +代+ 然后每次读取一行
代码2; | ---> +码+ 运行一下代码2
| ---> +2 +
| .... ++++
|
END{ | +++++++++ 最后文件读取完毕
代码3; | + 代码3 + 运行代码3
} | +++++++++ 只运行一次
_________________________________________________________
其实BEGIN{}
与END{}
块放的顺序和位置并不重要,也就是说可以这样
# 形式1
BEGIN{
代码1;
}
END{
代码3;
}
代码2;
------------------
# 形式2
END{
代码3;
}
代码2;
BEGIN{
代码1;
}
------------------
# 形式3
代码2;
BEGIN{
代码1;
}
END{
代码3;
}
其实除了这些由空格分隔的,我们平常使用的excel中的两个格式也是由特定的字符分隔的
- CSV文件 : 由逗号分隔的文本文件
- TSV文件 : 由制表符分割的文本文件
对于这种文件,使用这两个参数进行搭配,就省了很多事儿,是吧
# 例如一个文件 123.csv
# 新建一个txt文本文件,将后缀名改成csv就可以
# 内容为
name,apple,banana,orange,grape,strawberry
color,red,yellow,orange,purple,red
- 示例1
目标:打印出第一列,也就是标题
cat 123.csv | perl -n -a -F"," -e '
print "$F[0]\n";
'
# 输出
name
color
- 示例2
目标:计算出现了多少种颜色
cat 123.csv | perl -n -a -F"," -e '
# 如果第一列是color就执行代码
if($F[0] eq 'color'){
# 将第一个元素给扔掉
shift @F;
for my $color (@F){
# 利用哈希对重复的颜色的合并
# 而不是简单的记录这个列表中有多少元素
# 因为存在重复的颜色
# 红色是两份,它的值为2
$hash{$color}++;
}
}
END{
# 使用scalar方法得到哈西键的个数
print "Total number of color type : ",scalar(keys %hash),"\n";
}
'
---------------------------------------------
# 输出
Total number of color type : 4
- 示例3
目标:按照下面那样的方式打印出来(之间是逗号相隔开),这个其实就是列表的翻转,这个例子稍微有点复杂,这个例子意义其实不大。但是结合了多个perl单行程序
name,color
apple,red
banana,yellow
orange,orange
grape,purple
strawberry,red
# 代码开始
cat 123.csv | perl -n -a -F"," -e '
# 因为没有去处换行符,所以每一个元素后面均会带有回车符和换行符
# 这里将其除去
$F[-1] =~ s/\r*\n//;
my $title = shift @F;
my @items = @F;
my $item_num = scalar(@items) unless defined $item_num;
$title_num++;
# 列表里面的原始是有序的
# 用它来记录有顺序的title
push @title_list,$title;
# 哈希里面的元素是无序的
# 用它来记录每个title对应的该行的元素
$hash{$title} = \@items;
END{
$" = ",";
# 先输出标题行
print "@title_list\n";
# 然后打印出各个元素
for my $row (0..$item_num-1){
for my $key (@title_list){
print $hash{$key}->[$row];
print ",";
}
print "\n";
}
}
' | perl -p -e 's/,$//'
-----------------------------------------------------
# 结果
name,color
apple,red
banana,yellow
orange,orange
grape,purple
strawberry,red
补充说明
- -a与-F参数的顺序不重要,但是一定要放在-e参数之前
- -F指定分隔符的时候后面的分隔符要贴着-F参数,中间不要有空格之类,否则会报错
探讨
上面说到有时候文件夹会出现空格的情况,像上面出现的C:/Program Files/Git
被分隔的情况
那这样难道就没有办法来处理吗?
再来看一下df
的输出结果
df
------------------------------------------
# 输出
Filesystem 1K-blocks Used Available Use% Mounted on
C:/Program Files/Git 104857596 63822260 41035336 61% /
D: 318168060 313664144 125465657 40% /d
E: 41942012 21699268 20242744 52% /e
虽然按照空格来分隔可能有些行不通了,但是能不能转换一下思维,按照字符串的数量来划分
|-------------------|---------|---------|---------|----|----------|
Filesystem 1K-blocks Used Available Use% Mounted on
C:/Program Files/Git 104857596 63822260 41035336 61% /
D: 318168060 313664144 125465657 40% /d
E: 41942012 21699268 20242744 52% /e
可以看到每一列对应的字符串长度(空格也会被计算)是一致的
来!试一试
# 这个时候不能直接用空格分隔了,得采用一些特殊的方法
# 设置行数
export col=1
df | perl -n -e '
BEGIN{
# 设置想要打印的列数
# 传入环境中变量
$col = $ENV{'col'};
}
if($. == 1){
@title_slice = (22,11,11,11,5,11);
next;
}
my $offset = 0;
map {$offset+= $_} @F[0..$col-2] if $col-2 > 0;
print substr($_,$offset,$title_slice[$col-1]-1) =~ s/\s*(.+?)\s*/$1/r,"\n";
'
# 但是这样需要人工去数,也不是个好办法