014-懒惰也是一种美德

刘小泽写于18.7.19

所有的学习都是通过了解是什么、为什么后才有兴趣探究怎么做

Perl是什么?

Perl(Practical Extraction and Report Language)实用报表提取语言,1987年由Larry Wall设计,大写的P是语言本身,小写的p意为程序运行的解释器。

被称为“梦幻脚本语言”,Unix中的王牌工具,兼容了正则表达式,整合了awk、sed等一系列好用的小语法

他的优势在于:开源免费、跨平台、直接运行无需编译、无需定义各种变量及类型,不像其他语言上来就需要学习定义整型、浮点型等、perl很灵活,有单行模式,配合Linux十分好用

为什么用Perl处理生物数据?

Perl最大的优势就是处理字符串,而生信数据本质上就是将组织样本中的基因生物分子转为ATCGN这样的字符串来分析。

  • Perl内置了强大的正则表达式,善于处理字符串【例如我们常用的操作:统计序列ATCG含量、基因组GC含量、核苷酸翻译成氨基酸、序列提取某个片段、碱基互补配对】像这样的任务,其他语言可能需要十几行甚至几十行,但是Perl有时只要一行!
  • 处理生物数据,不可能只有一个样品吧。这么多的样品,如果你用在线工具,也需要一会的时间,但是Perl提供了批量化处理的操作,大大提高效率,本来一个小时的任务,你两分钟干完,剩下时间泡杯咖啡看集美剧都不为过!
  • 许多软件的结果可能并不是我们现在想要的,例如blast比对大量的样本,结果往往还需要进行过滤才能符合我们的要求,Perl可以对其进行筛选并且提取出想要的序列;文件格式的转换也常常用到Perl;另外许多软件也就是用Perl编写的,会了Perl在看他们的源代码就容易多了,方便纠错
  • Perl的作者就是语言学家,他开发的程序简单易懂

一个例子

一个🌰

数据结构

1 标量:

  • Perl中最基本的数据类型
  • 可为数字、字母,无需定义类型【其他语言需要提前定义类型:比如,整型还是浮点型,是单精度浮点型还是双精度浮点型】Perl中都是按照双精度浮点型进行保存和运算的,内部不存在整型。即使是整数也会被当成浮点数来计算,这样就省去了提前定义的麻烦
直接量:

程序中直接定义的一个值,例如-30,8e-3

数字操作:

常用的加减乘除、取模/取余数(%)【10%3=1】、乘幂3**2【=9】

字符串运算:

字符串是一连串的字符组合,字符可以是字母、数字、标点,可以为空,需要引号

生信数据比如处理序列就是处理字符串,例如给出“ACGCTAGCTAGCTCAGCTGGGCTAGCACGTCGATCGACTAGC” 计算他的长度、反向序列、互补配对、碱基替换、截取、翻译氨基酸【看!是不是就是那些所谓的网页小工具实现的内容?用Perl全能做到】

字符串中的引号差别:
  1. 单引号中的内容表示内容本身(但表示单引号和反斜线需要转义)
  2. 双引号支持反斜线转义,单引号不支持
字符串操作:

连接字符串:使用.连接两个文本,例如"hello"."world" 使用x 连接文本和数字,意思是重复几次,例如"fred"x35x4 表示5555,5*4 表示20】

未定义的标量$_:

$_=undef 其他语言都要提前定义类型,Perl不需要提前定义,就等于先找个位置,用着什么就变成什么,比如用在数值计算中,他的值就是0;用在字符串中就表示空字符串; 或者刚读进来的每一行可以当成undef,一会再处理

#!/usr/bin/perl -w
$_="hello,bioinfoplanet";
print
#结果就是hello,bioinfoplanet

2 标量变量

标量变量命名:

美元$ 表示,命令规范:

  1. 不能以数字开头
  2. 可以用字母下划线开头,后面可以加数字
  3. 区分大小写,尽量用小写
  4. 命令要能懂它的用途,勿求短
  5. 不要用内部保留的变量名
变量赋值:

单个等号是赋值【两个等号才是计算等于】

$dna="ATCACA";$gc_sum+=3; $dna.="ACTAG"; 得到的结果就是ACTAGACTAG

结尾以分号; 结束

3 数据比较

操作符优先级:

不需要记忆各个符号代表的优先次序,使用括号()可以自定顺序

比较操作符:
比较数值字符串
相等==eq
不等!=ne
小于<lt
大于>gt
小于等于<=le
大于等于>=ge

4 换行符

了解这个非常重要!因为:有时在Linux下显示很好的文本文件移动到windows上用记事本打开后,所有的序列都会放在一行,十分的混乱,这是因为~

Linux中是换行\n; Mac中是回车\r ;Windows是回车加换行\r\n

Linux中使用cat A 显示出文件结尾的换行符:linux下结尾是$ ,mac下的结尾就是^M ,windows下就是^M$

因此,这也说明了windows下的文本文件到了Linux中文件大小会增加,就是因为每行相比linux多了^M

怎样解决这个问题?

dos2unix: windows转换为linux unix2dos:Linux转为windows unix2mac:linux转为mac mac2unix:mac转linux

chomp函数 只去掉结尾的换行符,没有换行符就不起作用

chop 函数 每次可以切掉结尾的一个字符,运行一次结尾少一个,既可以切换行符,又可以切回车符

5 列表与数组

列表(list)是指标量的有序集合;

数组(array)存储列表的变量 例如@array=[1,3,5,7,9];@array就是一个数组,右边[1,3,5,7,9]就是一个列表

因此,打印的话,要么打印单个变量$,要么打印数组@

访问数组:

数组第一个值的下角标为0,$array[0]=1; $array[1]=3

统计数组中元素个数:

等于数组的最后一个角标$#array +1

【那么想要知道数组最后一个元素的值就可以用$array[$#array]或者$array[-1]

如何构建:
  • 使用括号将元素括起来,并且之间逗号隔开;
  • 使用范围操作符,适合连续加一的数值,例如@number=(1..20)就是1-20这20个元素构成的数组;
  • 建立简单的字符串列表:如果每个字符串都加引号会很麻烦,可以这样:既不需要引号,也不需要逗号@string=qw (i love bio info planet); 【qw意思是quoted word】 另外括号作为界定符号,可以进行更换,将 ()换成!! // {} <> 都可以
数组赋值:
($reed, $jessie, $ze)=("bio","info",undef);
print "$reed\n";
print "$jessie\n";
print "$ze\n";
得到结果
bio
info
	第三行为空

刘小泽写于18.7.20~练习了一段时间,发现最大的疏忽就是结尾的 切记

两个数组相关函数:split和join【也就是标量和数组间的转化】

split: 将字符串按照固定的分隔符进行切割,切完后得到一个数组 【后接三部分:分隔符,放在两个斜线之间;要分隔的字符串;分隔的份数,可以不写】

#!/usr/bin/perl -w
$test="bio;info;planet";
@array=split /:/, $test
print "@array\n"
# 结果是
# bio info planet【注意并不是输出一个换一行哦,而是整个数组在一起输出后再换行】

join:与split相反,将数组连接成一个标量 【后面冒号中的内容为分隔符,这里除了也可以用制表符\t,它等于四个空格,而用制表符分隔的一个好处就是:用excel打开后会识别这几项内容并显示在不同的列中】

$test2=join ":", @array;
print "$test2\n";
对数组进行操作:
  • 对数组尾部操作

    pop:弹出数组中最后一个元素

    **push:**向数组中最后一个位置加入一个元素【常用】

    pop & push

  • 对数组开头操作 shift : 取出开头第一个元素【常用】 unshift:在开头第一个位置加入一个元素

    #用Perl处理一个矩阵时,第一列往往是ID信息,可以提取出来方便后来排序
    while (<IN>) {
        chomp;
        my @line=split /\s+/,$_;  #\s匹配任意空白符,包括空格、制表符,+表示重复之前一次或多次
        my $id=shift @line; #my限定变量在当前循环体或函数体中
        print "$id\n";
    }
    
  • 对数组进行排序 sort:默认按照ASCII码由大到小排序,比如排序1-10,他会把10排在1的后面2的前面 reverse :逆向排序,不仅可以对数值还能对字符进行排序。例如:找到一段DNA序列的反向序列

    #!/usr/bin/perl -w
    $dna="ACGCTACGATCGACAGCATCGACTA"
    $dna_rev=reverse $dna;
    print "$dna\n"
    print "$dna_rev\n"
    
  • 遍历数组

    #!/usr/bin/perl -w
    @number=(1..6);
    # foreach格式
    foreach $num (@number){
        print "$_\n";
    }
    

6 上下文

同一个表达式,出现在不同的地方会有不同的意义【Perl中总是根据上下文来返回对应的值,主要看对标量进行操作还是对数组进行操作】

为什么要时刻关注上下文呢?

#!/usr/bin/perl -w
@name=qw(bio info planet);
@sorted=sort(@name);
print "@sorted\n"; # 结果返回 bio info planet
$num=10+@name;
print "$num\n"; # 结果返回13

这个例子告诉我们,在Perl这个灵活多变的大环境中,同样的表达式@name用在不同地方,他都是对的。再一次印证了Perl的哲学思想:解决问题的办法不止一个!

7 获得帮助

perldoc 获得内置帮助

-q xxx 在perldoc帮助文档中寻找包含xxx关键词的文档 -f 搜索Perl内置函数的功能 -v 搜索内置变量,如 perldoc -v @ARGV

perldoc perlfaq 查看关于Perl的经常被问到的问题及答案 perldoc perlop 查看Perl的操作符及优先级 perldoc perlsyn 查看Perl的的语法结构 perldoc perldiag 查看警告和错误相关的内容 perldoc perlvar 查看与变量相关的内容

内容读写

1 输入输出

使用open函数

#比如进行dna向蛋白质序列的转换
#!/usr/bin/perl -w
use strict;
open IN, "<dna.fa";
open OU, ">protein.fa";

输入输出整个过程有五部分:

  1. open函数打开文件

  2. 像IN/OU这种叫做文件句柄(Perl中处理进程与用户交互的名称,IN说明用户让Perl输入某个文件) 【关于文件句柄:命名由字母、数字或者下划线组成,不能以数字开头,通常都是用大些字母命名;内置6个,STDIN、STDOUT、STDERR、DATA、ARGV、ARGVOUT】

  3. 逗号分隔【不能省略!

  4. 引号部分是文件路径和名称

    <> 是输入、输出方向【一旦输入文件用了> 就会被清空】;而>>是追加,如果原文件不存在,就会创建一个新文件【和Linux重定向方式是一样的】

  5. 文件路径

上面👆的操作中,如果只有一个文件还好,如果有多个文件,那岂不是每次要修改open IN/out 的参数?

因此使用@ARGV就十分重要了!

@ARGV存储的是命令行的参数 ,例如:

#命令行运行 perl dna2pro.pl gene.fa pro.fa ref
# 其中dna2pro.pl是$0; gene.fa是命令行第一个参数,在perl语句中存储为$ARGV[0]; 以此类推,pro.fa是$ARGV[1]; ref是$ARGV[2]

2 读取数据:主要靠句柄

#假设命名为test.pl
#!/usr/bin/perl -w
open IN, "<$ARGV[0]";
$first=<IN>; #<>是钻石符,表示既可以是输入可以是输出,就把IN放在数据流符号的中间
$second=<IN>;
$third=<IN>;
print "$first\n$second\n$third\n";

命令行中输入perl test.pl dna.fa结果就得到前三行内容

但是这样一个一个定义有些麻烦,尤其行数比较多的时候,因此可以用while

#!/usr/bin/perl -w

open IN,"@ARGV[0]"; 
open OU,"@ARGV[1]";

while (<IN>){
    chomp;
    print OU "$_\n" #每行读入的内容存储在了$_中
}
close IN; #close关闭文件句柄
close OU;

命令行中输入perl test.pl in.fa out.fa 将读取行内容直接输入到out.fa中,不会打印到屏幕上

总结一下读写基本操作: 首先使用open函数打开文件,然后while循环和句柄读入文件,并使用换行符,进行处理后将结果输出,最后关闭句柄

3 格式转换

将fq转为fa:

区别两种格式: fq格式有四行,fa文件只有两行(即不需要fq的第三行和第四行); fq开头以@开头,fa文件开头是 >

#!/usr/bin/perl -w

open IN,"zcat $ARGV[0] | "; #针对压缩格式的fq文件,也不需要预先解压缩
open OU,">$ARGV[1]";

while ($id=<IN>) { #这样就将第一行命名为了id
    chomp ($id); #对读进来的第一行去换行符
    chomp ($seq=<IN>); #对读进来的第二行(命名为序列行)去换行符
    <IN>;
    <IN>; #第三、四行用不着,所以不需要操作
    $id=~ tr /@/>/; # 替换第一行的@为>,使用tr函数
    print OU "$id\n";
    print OU "$seq\n";
}
close IN;
close OU;

写于18.7.22

哈希

哈希数据是没有顺序的;使用键值对,键是唯一的,值可以相同;使用大括号调取键【数组是用中括号,这个区别要注意】;引用哈希变量用%【标量变量用$,数组变量用@ 】;哈希中存储的键值对形式:键=>值

定义空哈希:

%hash=();

将列表改成哈希:

例如有这么一份表格,表示字母与数字的对应:

wordAone
wordBtwo
wordCthree
wordDfour
wordEfive
#!/usr/bin/perl -w
use Data::Dumper; #这个模块可打印出哈希的数据结构
%hash=();#先定义一个空的哈希,一会向其中存入内容

open IN,"<$ARGV[0]"; #文件重定向输入
while (<IN>){ #一行一行读入该文件
    chomp; # 每一行例如wordA one 去掉结尾的换行符
    @line=split /\s+/,$_; #按照空格分隔,将两列存储在line数组中【$_是split的作用对象,也就是读入的每一行】
    $hash{$line[0]}=$line[1];#将第一列作为键,第二列作为值存储在之前的空哈希中。
} 
close IN;

print Dumper (\%hash); #print加Dumper模块,再引用哈希

对哈希进行遍历:

  1. keys 函数
# keys函数也常常用在对哈希的遍历过程中
#keys和value函数定义键和值,保存到数组并打印
@key=keys %hash;
@value=value %hash;

print "@key\n";
print "@value\n"
#返回的键值对还是一一对应的,但是键值对之间的顺序和原来列表不同

#如果将键用$存到标量而不是用@存到数组,那么返回的就是键的个数
#$key=keys %hash; 结果返回的就不是具体的键wordA-E,而是haxi的元素个数5
  1. 对哈希进行遍历也可以用foreach
# 实际上foreach是用来对数组进行遍历的,这里的@tmp就是用keys对哈希进行遍历再排序得到的数组
@tmp=sort keys %hash;
foreach (@tmp){
	print"$_\n"
    print "$hash($_)\n;"
}
  1. while循环中用each
while (($key,$value)=each %hash){
    print "$key\n$value\n";
}

检测哈希中某个键是否存在:

if (exists %hash{wordA}) {
    print "$hash{wordA}\n";
}

用哈希进行序列提取:

根据固定的id从另一个文件或者数据库提取出序列

一般处理方式是:将小的数据集存储到一个哈希中,然后再遍历大的数据集,每次判断id在哈希中是否存在,如果存在就输出,不存在继续循环

就想NCBI blast过程,就是根据id去找序列的过程。【那么如果将大的数据集存到哈希中,用小数据集遍历可以不可以实现呢?其实也可以,但是会消耗大量的资源,不值得!就想去blast,如果将大的NCBI nt数据库存到哈希,那么这个数据库有80多个G,是很慢的】

# 第一步:将gene id【第一个参数】存储到哈希中
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
if (scalar @ARGV==0){ #添加帮助信息,如果ARGV这个命令行参数个数为0,即没有输入的id文件和基因序列文件
    die "Usage: This program is used to extract sequence by a gene id from a gene list 
    	perl $0 <id list> <fasta file>";
} #die函数终止程序,并打印返回信息;$0意思是程序的名字

my %hash=();
open IN,"<$ARGV[0]";
while (<IN>) {
    chomp;
    $hash{$_}=1;
}
close IN;
#print Dumper (\%hash);

# 第二步:读取基因序列文件【格式是每个id对应一条序列】
open FA,"<$ARGV[1]"; # 打开fasta格式序列文件
$/=">";<FA>; #根据fasta的格式,将分隔符$/定义为>,这样每次读入的数据就是一个字段,也就是一个完整的基因序列。包括了id和序列
while (<FA>) { # 每次读入一个分隔后的基因
    chomp;
    my id=(split /\n/$_,2)[0]; #将id与序列分开~利用换行符进行分隔,存为一个为定义标量。最后加上的数字2,意思是将标量分成两份,也就是id行和序列行。然后就可以根据基因id,即分隔后的[0]进行判断
    if (exists $hash{$id}){ #判断第一步生成的哈希中的值在id中是否存在
        print ">$_"; #因为原来gene id是带>的,但是$id中用$/=">"处理后就不带>,所以最后输出还需要加上>;另外输出的结果是$_,即id+序列
    } else {
        next; #一个一个判断,如果不存在就检查下一个
    }
    print "$id\n"
}
close FA;

写完脚本可以用perl -c 查看脚本是否正确

子程序

之前用过了chomp、print等,perl还能让用户自定义函数,方便利用已有的代码

使用sub定义【不能以数字开头,最好名字和函数功能相关,不要和内置函数重名】 每个子程序都有一个返回值,而运行子程序要的就是返回值

最简单的使用:

#以打印生信星球信息为例
#!/usr/bin/perl -w
use strict; #代码中有不好的编码风格,那么提示编译失败

&print_planet; # &符号可以调用子程序【定义的和系统不冲突时可以省略&,冲突时必须加上&】
sub print_planet {
    print "Hello, bioinfoplanet\n";
}
&print_planet;
#这样结果就会输出两次"Hello, bioinfoplanet"

写于18.7.23

子程序的参数与返回值–输入与输出:

上面的简单使用,没有加上任何参数就会输出字符串这样一个标量;

当然,也可以对子程序输入数组、哈希、文件等参数,然后输出标量、数组或者哈希。

默认将子程序的最后一个表达式的结果作为子程序的返回值,如果想返回中间一个表达式,使用 return 函数

这里以一个加法运算来学习:
#!/usr/bin/perl -w
use strict;

sub sum {
    my $num1=shift @_; #定义第一个标量变量为@_中取出的第一的第一个元素
    my $num2=shift @_;
    my $total=$num1+$num2;
    return $total;
}

my $total=&sum (3,5);# &sum()括号中的标量会存储到@_这个数组变量中;括号中有几个参数,@_数组中就有几个元素
print "$total\n"

统计fasta信息的子程序:

程序思路:输入有一个,gene id与序列文件对应的哈希列表【格式为id=序列文件】。输出要有三列,一列是gene id,一列是基因数量,一列是基因平均长度。

读取fasta文件时,将$_修改分隔符为>,这样一次就能读进来整段序列,将每段序列存储到哈希中,序列的id为哈希的键,序列为值

#!/usr/bin/perl -w
use strict;

open IN,"<$ARGV[0]"; #输入的第一个文件是基因id与序列对应的哈希文件
while <IN> {
    chomp;
    my ($id, $file)=split( /=/,$_)[0,1]; #哈希文件的格式是id=序列文件,因此用=分隔二者,存到$_,再将他的第一列赋给变量$id,第二列赋给变量$file
    my ($gene_num,$gene_len)=&gene_stat ($file) #定义基因数量、长度的变量,子程序是gene_stat,对上面得到的$file也就是序列文件进行操作
    print "$id\t$gene_num\t$gene_len\n";
}
close IN; #while循环是主体,其中包括了子程序

sub gene_stat { #开始定义子程序
    my $file=shift @_; #先定义几个变量:file就是刚才得到的数组变量@_中元素,定义基因数量、长度初始值为0
    my $gene_num=0;
    my $gene_len=0; 
    
    open FA,"$file"; #将读入的$file序列文件作为FA句柄
    while (<FA>){
        chomp;
        if (/^>/) { #判断读入的文件开头为>,则作为基因数目
            $gene_num+=1;
        } else {
            my $len=length ($_); #否则作为基因序列,统计长度,一会计算平均长度用
            $gene_len+=$len
        }
    }
    close FA;
    my $avg_len=$gene_len/$gene_num;
    return $gene_num, $avg_len;
}

Perl常见问题:

Perl的编程过程中,会出现各种各样的问题,需要不断调试。有时候很头疼的就是,即使按照别人的程序敲一遍还是会报错

错误一:忘记加分号

分号在Perl中代表一个完整的语句,忘记加分号就会提示语法错误,并且运行会给出具体出错行数

这里显示第四行有问题,如果在一个大程序中,要快速跳转到某一行,使用vi +4 1.pl ,因为print上一行没有分号

忘记加分号

错误二:是否需要用my定义

很多时候需要使用use strict 编译命令,这是就要要使用my 来编译变量,而且不能重复定义

my 与 use strict

#使用strict的例子:
#!/usr/bin/perl -w
use strict;

my $total;
my $num1=2;
my $num2=1;
$total=$num1+$num2;#这里的total因为之前定义过了,所以不用再使用$total
print "$total\n";

错误三:拼写错误

这个在很多编程中都会存在,拼写错误会提示变量没有被声明

这个问题在文本编辑器如Vim中会帮助我们解决,如果某个变量输入错误,比如print输入为prnit ,Vim会通过颜色不同(正确有颜色,错误为白色)

对于变量输入,最好使用vim自带的ctrl+Nctrl + P 的自动补全功能,这样可以避免错误

错误四:大括号的使用

尤其使用if嵌套循环时,每一个大括号是一个程序块,如果缺失某一对配对的大括号其中一个,就会提示Missing right/left curly or square bracket at line xxx ;如果大括号多了一个,会提示Unmatched right/left curly bracket at line xxx

避免报错小技能:

  • perl -c (check)运行之前先检查一下是否书写有问题
  • perl- d (debug)一种交互的perl编程模式,适用于长的脚本。l 列出命令的10行;如果要列出某几行,就用l 4-7这样的l+行号 详细信息可以参考 perldoc perldebug

正则表达式:重要的特性!

Perl的正则表达式是内建的,为Perl提供了快速、灵活的字符串处理

Perl中的正则也叫做“模式匹配”,是用来匹配某字符串的模版; 日常使用中,模式匹配无处不在,例如使用搜索引擎,在搜索框内输入关键字,就能返回信息,他就是利用了正则表达式

一个简单的例子:

一个简单的例子

元字符:

  • . 替换单个字符(一对一替换),不能替换换行符

  • \ 转义符,例如匹配.或者\本身要使用\

  • 表示数量的* + ?

    • * 【例如在root用户下,使用rm -rf * 叫做“自杀模式”】,与.配合使用表示任意字符出现0次或多次 【0—∞】

    • + 表示至少一次 【1—∞】

    • ?表示0次或1次【范围最小】

    • 花括号中指定重复次数,例如匹配1-5次bioinfo中的obio{1,5}info

      如果要制定整个单词次数,用小括号进行分组/(bioinfo){3}/ 这个表示匹配bioinfo3次;/(bioinfo)+/表示匹配bioinfo一次以上

  • 关于字符集:

    • 匹配字符集的单个字符/[bcdefwz]at/ 匹配bat、cat、fat其中一个,等同于/(b|c|d|e|f|w|z)at/ 或者更简单的/[b-fwz]at/ (表示b-f连续的字母加wz)
    • [a-z]、[A-Z]、[0-9]简写为\d+[a-zA-Z0-9_]也就是单词字符,简写为\w
    • \s+ 表示空白,例如split /\s+/,$_;
    • 反义字符集:利用^ ,例如 [^0-9] 等于[^\d] 等于\D,表示不匹配数字 【反斜线加大些字母表示不匹配】
  • 要排除某个匹配,用!

#来吧!请上手操作一下吧!
#打开你的Linux terminal,新建一个perl脚本

#!/usr/bin/perl -w
use strict;

print "Do you like bioinfoplanet?\n"
my $choice=<STDIN>;

if ($choice =~ /y e s/ixs) { #匹配的时候用 =~ 加模式/ / 
    print "Thanks, good job!\n"
} else {
    print "OK, we'll do better!\n"
}
# 介绍一下$choice =~ /y e s/ixs中的修饰符
# i忽略yes其中各个字母的大小写;
# x可以忽略模式中的空白【常常针对模式较长,都是点号、星号、字符集等,这样写在一起就看着很复杂,需要加空格隔开,但是运行程序的时候又想让他们连在一起,所以用x】
# s可以让点号匹配到换行符,这样即使 "Do you like bioinfoplanet?"这一句是分行显示的,也可以匹配成功
# m:多行匹配,跨行模式匹配
# g全局匹配
生信模式匹配小练习:

现在有一些序列,下载地址https://github.com/Bioinfoplanet/Perl-Test.git 打开后是这样:

测序序列

想寻找开头是CCG,结尾是TTG的序列

#!/usr/bin/perl -w
use strict;

open IN,"<$ARGV[]0";
while (<IN>) {
    chomp;
    if (/^CGG.*TTG$/i) { #^表示开头匹配,$表示结尾匹配
        print "$_\n";
    } else {
    	next;
    }
}
close IN;
#补充一个vim小技巧,利用d(删除)再结合^或$,可以表示删除光标到行首/尾
单词锚定:

单词锚定

挖掘文本信息:

一般本地blast的format 0结果中,会有匹配的分值(Score)、期望值(Expect)、一致性(Identities)、准确度(Positives)、Gaps等信息,可以挖掘这些信息

#比如这里有一个blast format0的结果,result1中想要数字部分,result2想要括号中的百分数部分
#!/usr/bin/perl -w
use strict;

my $result1="Score = 150 bits (308), Expect = 1e-34, Method: Compositional matrix adjust.";

if ($result1=~ m/Score\s*=\s*(\d+)\s*(?:bits|Bits)\s*\(\d+\)\,\s*Expect\s*=\s*(\S+),\.*/) { #匹配过程看起来很复杂,其实也就是一点点填充的过程,有空格就用\s*,有数字就用\d+,对于中间没有空格的整体如1e-34就直接使用\S+。就像这样把整个句子用元字符的方式重绘出来,方便大量统计
    print "$1\t$2\n"; #$1与$2就是捕获之前分组的变量
}
#得到的结果就是 150	1e-34

#按说每一个括号就是一个分组,而后面用$来捕获变量也是根据括号,那么比如(bits|Bits),我们只想让他用来分组,表示匹配bit或者Bits。但是这样有一个问题,后来引用的时候就会讲这一部分也输出;为了避免这个问题,就在左边括号后面加上?:表示这个整体就是一个分组,别把它当变量使用

my $result2="Identities = 131/468 (28%), Posotives = 217/468 (46%), Gaps = 46/468 (10%)";
if ($result2=~ m/(\S+)/g) {
    print "$result2\n";
}
改进版:
#!/usr/bin/perl -w
use strict;

my $result1="Score = 150 bits (308), Expect = 1e-34, Method: Compositional matrix adjust.";
my $result2="Identities = 131/468 (28%), Posotives = 217/468 (46%), Gaps = 46/468 (10%)";

my @info1= ($result1=~ m/Score\s*=\s*(\d+)\s*(?:bits|Bits)\s*\(\d+\)\,\s*Expect\s*=\s*(\S+),\.*/);
print "@info1\n";
#直接将捕获的变量存储到列表中,即使没有匹配到,也只是会输出一个空列表
my %info2= ($result2=~ m/(S+)/g); #加上g意思是将全部符合条件的找出来
print "@info2\n";

另外还有一个s(替换模式),例如

#!/usr/bin/perl -w
use strict;
my $line="hello, bioinfoplanet";

$line=~ s/hello/nihao/;#结果就将hello变成了nihao
$line=~ s/^/Hi,/; #结果为Hi,nihao, bioinfoplanet
$line=~ s/$/hi/; #结果在结尾加上一个hi
$line=~ s/hello//g; #将全部hello替换为空,大规模替换要加上g
print "$line\n";

对序列进行处理:

  1. 得到序列的反向/互补序列
#!/usr/bin/perl -w
use strict;

my $seq="TTCGATGCTAGCATCGATCGCTAGCATCGCTAGCATCCCGATTGACTAATCAT";
my $qes=reverse $seq; 
print "$qes\n";#序列反转
$qes=~ tr/ATCGN/TAGCN/; # 序列碱基互补
print "$qes\n";

#结果1得到:TACTAATCAGTTAGCCCTACGATCGCTACGATCGCTAGCTACGATCGTAGCTT
#结果2得到:ATGATTAGTCAATCGGGATGCTAGCGATGCTAGCGATCGATGCTAGCATCGAA

  1. 序列全变大些/小写
#!/usr/bin/perl -w
use strict;

my $seq="TTCGATGCTAGCATCGATCGCTAGCATCGCTAGCATCCCGATTGACTAATCAT";
$seq =~ s/(\w+)/\L$1/g; #(\w+)是将所有字母分成一组,\L是小写[\U是大写],$1是捕获前面的分组【另外\u是更改首个字母大写;\l首字母变小写】
print "$seq\n";

#结果得到:ttcgatgctagcatcgatcgctagcatcgctagcatcccgattgactaatcat

另外这些大小写语句除了在s模式下使用,还能在双引号内使用,例如在print 语句中格式化输出 print "\Uhello, wor\Eld" 输出结果是HELLO, WORld 【注意这里的\E是终止前面\U命令的意思】

  1. 贪婪匹配 – 匹配尽可能多的内容

一般. + ? 会匹配尽量多的内容,比如上个例子$seq 中,如果匹配模式是$seq=~ /^TTC(.+)CAT/i ,即使中间也出现了CAT,程序也不会停下,他会一直匹配到结尾的CAT,这就是贪婪;

贪婪的对立面就是节俭,这种模式可以在. + ? 的基础上加上? ,也就是$seq=~ /^TTC(.+?)CAT/i 就输出最短的序列

  1. 格式化序列

有时一个fasta文件中序列长短不一,并且有的换行有的不换行,这样看起来就很难看。这时就可以将序列格式化,让每行保证一定量的字符【可以自定义】,并且加换行符。

格式化序列

测试fasta下载: wget https://github.com/Bioinfoplanet/Perl-Test/blob/master/unformated.fasta

#!/usr/bin/perl -w
use strict;

open FA,"<$ARGV[0]";
$/=">";
<FA>;
while (<FA>) {
    chomp;
    my ($id,$seq)=(split /\n/,$_,2)[0,1];
    $seq=~ s/\n//g;
    $seq=~ s/(\w{$ARGV[1]})/$1\n/g; #每x个字符作为一个$1,也就是前面选取的(\w{})得到的内容,注意\w后面是大括号而不是小括号
    print ">$id\n$seq"
}

写于18.7.25

  1. 数据加千分位

一大长串数字如果加上千分位将会很容易观察,千分位只在整数部分添加(从后往前),小数部分不添加

#!/bin/bash/perl -w
use strict;

my $num=123456789.98;
1 while $num=~ s/^(-?\d+)(\d\d\d)/$1,$2/; #建立一个死循环,匹配模式:正或负(-?)数(\d+),后面的\d\d\d是从后向前每三个数划分一次,每次循环的结果即每三个数字用逗号隔开
print "$num\n";

#结果就是123,456,789.98

或者可以通过定义子程序,之后就可以直接用子程序来添加

#!/usr/bin/perl -w
use strict;

my $num=123456789.98;
my $result=&delimiter ($num);
print "$result\n";

sub delimiter {
    my $data=shift @_;
    1 while $data=~ s/^(-?\d+)(\d\d\d)/$1,$2/;
    return "$data\n"
}
  1. 输出特定长度的序列

有时需要对fa序列提取部分序列进行比对,提取的起始位置、终止位置以及输出的每行长度都可以定义

9.png

基因组信息统计:

#!/usr/bin/perl -w
use strict;

#先定义全局变量,也就是从赋值到输出结果一直有效的变量
my $total_len=0;
my $total_num=0;
my $total_gap=0;
my $total_gc=0;
my @len_array=(); #排序之后取出首尾元素,也就是最短和最长序列

print "ID\tGeneLength(bp)\tGapLength\tGCcontent(%)\n"; #添加表头
open IN,"<$ARGV[0]", or die "can not open this file$!\n";
$/=">";<IN>; # $/代表默认的输入记录的分隔符,默认为\n,这里指定为>
while (<IN>) {
    next if (/^\s+$/); # 如果行为空则不处理
    chomp; # 去除$/(也就是默认的\n)
    my ($id,$seq)=(split /\n/,$_,2)[0,1]; # 将id与seq行分开
    $seq=~ s/\n//g; #对seq行进行统计前,要把seq中的换行符去掉
    my $len=length ($seq);
    push @len_array,$len; #向数组最后一个位置存入
    $total_len+=$len;
    my $gc+=($seq =~ s/G/G/g + $seq =~ s/C/C/g); #通过全局替换获得G、C字符的个数
    my $GC=($gc/$len)*100; #gc含量等于G和C个数之和除以每条序列的长度
    my $gap+=($seq =~ s/N/N/g);
    $total_gap+=$gap #将计算的gap数加入全局变量中
    $total_gc+=$GC;
    print "$id\t$len\t$gap\t";
    printf "%.2f\n","$GC"; #格式化输出,让GC结果保留小数点后两位
    $total_num++; #代表一次循环结束
}

my $avg_len=($total_len/$total_num);
my $total_GC=($total_gc/$total_len)*100;
my @sort_len=sort {$a<=>$b} @len_array; #将数组中序列长度排序,方便下面输出最长最短序列

print "\nTotal Stat:\n";
print "Total Number {#}:$total_num\n";
print "Total Length (bp):$total_len\n";
print "Gap(N)(bp):$total_gap\n";
print "Average length(bp):%d\n", "$avg_len"#整数输出
print "Minimum Length(bp):$sort_length[0]\n";
print "Maximum Length(bp):$sort_length[-1]\n";
printf "GC content(%):%.2f\n","$total_GC";


Yunze Liu
Yunze Liu
Bioinformatics Sharer

Co-founder of Bioinfoplanet(生信星球)

Next
Previous

Related