【上集回顾】
上次说到了-p
与-n
参数,其实再加上之前学的-e
参数已经可以做很多事情了,但是为了方便,Perl还有这样一对搭档组合的参数,就是-a
和-F
【参数解释】
-a : 将读入的$_
进行分割,保存到@F
列表之中,类似于split /分隔符/ , $_;
而这个分隔符是由-F
参数指定的,其实这个功能与awk工具相似
-F : 在添加-a
参数时候,指定分隔符(可以是正则表达式),如果不加好像是由空格作为分隔符,一般对其进行设置
实例说明
为了更加清晰的说明,还是举例子吧,打开终端或者git for windows
输入
1 2 3 4 5 6 7 8 9 10 11 12 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+(意思是按照一个或者多个空格或者制表符分隔,这里也可以改为" +"),将$_
分割为三份)
1 2 3 4 5 6 7 8 9 10 11 qwe asd zxc ^ ^ | | 空格 # 根据空格来划分 \s+ 表示如果有多个空格相连也一并视为一个整体 # 切割之后,空格都消失 / / qwe/asd/zxc 成为 @F中的元素 ('qwe','asd','zxc') / /
分隔的三份按照顺序存在@F
列表中
遍历@F
列表,将其中的内容打印出来
其实你可能会说,用之前之前学的参数就够了啊!比如
1 2 3 4 5 echo "qwe asd zxc" | perl -n -e ' my @F = split /\s+/,$_; $" = "\n"; print "@F\n"; '
那为什么要这样做呢?其实在平常的文本中比没有感觉到,在linux或者mac系统下面,有很多信息就是以文本的形式给出来的,而且中间一般都是用空格或者制表符分隔的,就比如使用df命令查看磁盘使用情况
1 2 3 4 5 6 7 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:我需要将所有的盘符提取并打印出来?
来试一下:
1 2 3 4 5 6 7 8 9 df | perl -n -a -F"\s+" -e ' print $F[0],"\n"; ' ------------------------------------------ Filesystem C:/Program D: E:
可是标题行不是我想要的,怎么除去呢? 有多种方法
使用特殊变量$.
$.
意为当前读取的行数
1 2 3 4 5 6 7 8 9 10 11 12 13 df | perl -n -a -F"\s+" -e ' # 第一行就是标题行了,直接跳过它 if($. == 1){ next; }else{ print $F[0],"\n"; } ' ------------------------------------------ C:/Program D: E:
借助Linux命令
1 2 3 4 5 6 7 8 9 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盘,原因见上述说明)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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的单行程序中会经常用到,说明一下它们两个的作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 单行程序中的结构 # 流程解释 _________________________________________________________ BEGIN{ | +++++++++ 读取文件之前 代码1; | + 代码1 + 就运行代码1 } | +++++++++ 只运行一次 | | ---> ++++ | ---> +代+ 然后每次读取一行 代码2; | ---> +码+ 运行一下代码2 | ---> +2 + | .... ++++ | END{ | +++++++++ 最后文件读取完毕 代码3; | + 代码3 + 运行代码3 } | +++++++++ 只运行一次 _________________________________________________________
其实BEGIN{}
与END{}
块放的顺序和位置并不重要,也就是说可以这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 形式1 BEGIN{ 代码1; } END{ 代码3; } 代码2; ------------------ # 形式2 END{ 代码3; } 代码2; BEGIN{ 代码1; } ------------------ # 形式3 代码2; BEGIN{ 代码1; } END{ 代码3; }
其实除了这些由空格分隔的,我们平常使用的excel中的两个格式也是由特定的字符分隔的:
CSV 文件 : 由逗号分隔的文本文件
TSV 文件 : 由制表符分割的文本文件
对于这种文件,使用这两个参数进行搭配,就省了很多事儿,是吧
1 2 3 4 5 # 例如一个文件 123.csv # 新建一个txt文本文件,将后缀名改成csv就可以 # 内容为 name,apple,banana,orange,grape,strawberry color,red,yellow,orange,purple,red
目标 :打印出第一列,也就是标题
1 2 3 4 5 6 7 cat 123.csv | perl -n -a -F"," -e ' print "$F[0]\n"; ' name color
目标 :计算出现了多少种颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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单行程序
1 2 3 4 5 6 name,color apple,red banana,yellow orange,orange grape,purple strawberry,red
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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
的输出结果:
1 2 3 4 5 6 7 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 2 3 4 5 |-------------------|---------|---------|---------|----|----------| 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export col=1df | 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"; '