生物信息学与算法码神之路:Perl篇Perl小推车

perl One-Liners | perl命令行学习3 -a和

2018-07-28  本文已影响10人  白菜代码小推车

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

这次的例子要比之前的例子复杂一点,我来一一说明

  1. echo在屏幕上打印出qwe asd zxc这个字符串,这字符串中间由空格分成三个部分,分别是qwe、asd、zxc
  2. echo的输出进入管道 |
  3. perl逐行读取(因为只有一行,所以直接读完了)
  4. 读取的字符串赋值给$_
  5. $_被分割为三份(应-a的要求,根据-F(注意双引号是贴着-F参数的,中间没有空格隔开)的指定的\s+(意思是按照一个或者多个空格或者制表符分隔,这里也可以改为" +"),将$_分割为三份)
qwe asd zxc
   ^   ^
   |   |
   空格

# 根据空格来划分 \s+ 表示如果有多个空格相连也一并视为一个整体
# 切割之后,空格都消失

    /   /
qwe/asd/zxc    成为 @F中的元素  ('qwe','asd','zxc')
  /   /
  1. 分隔的三份按照顺序存在@F列表中
  2. 遍历@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:

可是标题行不是我想要的,怎么除去呢?
有多种方法

  1. 使用特殊变量$.

$.意为当前读取的行数

df | perl -n -a -F"\s+" -e '
  # 第一行就是标题行了,直接跳过它
  if($. == 1){
    next;
  }else{
    print $F[0],"\n";
  }
'
------------------------------------------
# 输出
C:/Program
D:
E:
  1. 借助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中的两个格式也是由特定的字符分隔的

对于这种文件,使用这两个参数进行搭配,就省了很多事儿,是吧

# 例如一个文件 123.csv
# 新建一个txt文本文件,将后缀名改成csv就可以
# 内容为
name,apple,banana,orange,grape,strawberry
color,red,yellow,orange,purple,red

目标:打印出第一列,也就是标题

cat 123.csv | perl -n -a -F"," -e '
  print "$F[0]\n";
'

# 输出
name
color

目标:计算出现了多少种颜色

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
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

补充说明

探讨

上面说到有时候文件夹会出现空格的情况,像上面出现的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";
'
# 但是这样需要人工去数,也不是个好办法
上一篇下一篇

猜你喜欢

热点阅读