PERL语言编程入门
本章介绍生物信息学中常用的编程语言Perl的基本概念与方法,希望读者能掌握编程的基础知识,并在以后的生物信息分析实践中提高编程技能。
生物信息学应用中最常见编程语言是Perl或Python。Perl是一种动态的、直译式编程脚本语言(官网www.perl.org)。Perl由Larry Wall于1987年设计,主要用于处理文本数据,Perl是"Practical Extraction and Report Language(实用报表提取语言)"的缩写。因此非常适合用于基因序列处理,可能是老一辈生物信息学家最常用的编程语言。Perl语言非常灵活,设计哲学是"there's more than one way to do it"。Perl的灵活性及“过度”的冗余语法导致代码难以阅读,不利于代码的维护。而另一种编程语言Python具有非常简捷而清晰的语法,虽然没有Perl灵活,但便于代码阅读与维护。Python是Guido van Rossum在1989年设计的一种面向对象、直译式程序设计脚本语言(官网www.python.org)。由于其功能强大而完善,用户数量急剧增加,现已成为使用最广泛的通用型编程语言。
本书以Perl语言为例,简单介绍编程的基本概念与方法,更多的内容请参阅经典教材Learning Perl (俗称小骆驼书)[1]。当掌握Perl编程的基础知识,你会发现学习其它语言也变得非常容易了,如Python, C/C++, Java等。
第一个Perl程序
Perl程序是一个包含Perl语言命令的文本文件,也称为Perl脚本(Script)。因为计算机程序与电影脚本类似,都是规划每一步要做什么。Perl是一种不需要编译的程序语言,但一个Perl脚本也要用Perl解译器(Perl interpreter)来运行。Perl解释器的命令为“perl” (注意全是小写字母,而首字母大写Perl代表Perl语言本身),Perl语言版本可以用命令”perl -v”来查看。 具体来说,编写一个Perl程序包括以下两个步骤:
- 首先,文本编辑器建立一个文本文件。一般Perl脚本以”.pl”作为文件名后缀,如”hello.pl”;
- 然后,运行解释器来执行Perl脚本。启动Perl解释器的方式为在Linux或Windows的命令窗口输入“perl hello.pl”运行脚本,这里hello.pl为要运行的脚本名。
我们以一处简单的Perl脚本体验一下Perl语言的魅力。先看下面代码:
Perl脚本的第一行(”#!”开始)用于指定Perl安装路径。其它以符号“#”开始的句子被认为注释,注释信息有助于了解程序的功能。如果注释内容一行写不完,可在下一行开头加入“#”,再继续写。 Perl程序的基本执行单位是语句,一条语句以分号(;)结尾。有时一条语句会超出一行代码,由于Perl解释器会通过查找分号位置,所以不难判断一个语句在哪里结束。程序块(block)是一组用花括号括起来的语句,每一个代码块以开括号({)开始,并以闭括号(})结束。#!/usr/bin/perl –w #"-w"表示发现错误就报警 #First Perl Script print "Hello World!\n"; #"\n"是换行符
编程实践
用文本编辑器(Notepad2)新建一个文件,输入上面的代码,并保存为hello.pl文件。
在Linux终端通过以下命令来运行脚本:
$perl hello.pl
该程序的运行结果是在屏幕上显示:”Hello World!”。正确运行的结果如图1所示。
注意:Perl解释器有一个参数(-c)可以只检查语法错误,不运行脚本。
另外,还有一个参数(-e)可以快速运行脚本:
$perl –e 'print "hello world!\n";'
利用好Perl的一行命令模式运行Perl脚本可以大大提高编写代码的效率。
变量与值
变量是计算机编程的重要概念之一。要理解变量,需要我们了解数据是如何存储在计算机的内存中。一个数据,例如数字或字符串(一组字母或数字字符),必须存储在存储器内的某个特定位置(或“地址”);要访问它,必须以某种方式识别它的放置位置。变量就是一个命名的存储位置,用来存储一个数据。变量一般用来字母命名,合法的变量名可由字母、数字和下划线(_)等字符组成。对于Perl,变量名之前还必须要有专门的符号指明变量类型。美元符号表明此变量是一个标量(scalar variable),只能用于存放一个值。此处先只介绍标量,本章后面的数据结构部分还会介绍其它类型的变量,如数组可以支持多个值。
存储在变量里的值可以通过赋值语句来得到或改变。赋值语句的一般语法:variable = expression。右侧表达式可以包含另一个变量、常量、算术表达式或函数调用等。尽管赋值语句的右侧(=符号右侧的部分)可能非常复杂,但左侧始终是变量。这是因为赋值语句的目的就是更改变量存储位置的内容。
$x = 3;
$y = 7;
$sum = $x + $y; #变量$sum的值是10
$remainder = $y % $x; #7除以3的余数是1
数据类型
Perl语言中一个变量可以是整数,如7,或一个实数,如3.14159,还可以是一个字符串(string),如“Hello World”,及其它类型。许多编程语言要求在使用变量前要先声明变量能包含什么样的值,即指定与变量关联的数据类型,如字符串,整数,十进制数等。但Perl语言的一个变量可以在任何时候包含任何类型的数据。Perl创建变量时唯一要做的决定是这个变量的类型,是支持一个值的标量用美元符号($),还是支持多个值的数组(@)或哈希表(%)等。
变量在第一次声明的时候会被自动创建。一些例子如下:
$my_variable = 3.14159;
print (“my variable contains the value: $my_variable\n”);
#printf格式化数字
printf (“my variable contains the value: %.3f\n”, $my_variable);
printf (“my variable contains the value: %.2e\n”, $my_variable);
$my_variable = “Hello World”;
print (“my variable contains the value: $my_variable\n”);
$my_variable .= “!”;
print (“my variable contains the value: $my_variable\n”);
将上面的代码输入到一个文本文件,并在Perl里执行,就会看到变量$my_variable先包含一个实数(3.14159),然后用printf命令格式化数字,只保留小数点后3位(%.3f),或以科学计数法表示,且只保留2位小数(%.2e)。
最后,变量被赋予一个字符串(“Hello World”),再使用字符串连接运算符将感叹号添加到现有文本字符串的末尾(“Hello World!”)。
有时候需要对字符串和变量进行操作,例如想把字符“y”附加到一个字符串变量后面,可以用下面的代码:
$name = "Mike";
$nickname = "${name}y"
在美元符号后面用大括号把变量名包含起来,这样${name}就和$name一样,但让变量名在哪里开始和结束更清楚,不然Perl将寻找不到变量$namey。
[!TIP] 单引号与双引号的区别:
- 单引号:完全直白的翻译引号里的全部内容
print ('Hello $my_variable\n')
# Hello $my_variable\n- 双引号:可以识别引号里的变量和反义字符
print ("Hello $my_variable\n")
# Hello world
基本运算
Perl包含大部分数学与字符串的运算符。表1是Perl的常见运算符及其作用。为了方便编写程序,Perl还有一些运算符的组合,如下面这个例子:
$var1 = $var1 + 2;
$var1 += 2;
上面两行Perl代码的结果是完全一样的,运算符“+=”可使得从一个变量加上一个数的运算变得简单。
表1 常用的Perl运算符
运算符 | 作用 |
---|---|
*, /, +, - | 加、减、乘、除 |
% | 取余数 |
++, -- | 增量或减量,如$a++表示$a数值加上1 |
+=, -=, *=, /= | 合并运算,如$a += 8相当于$a = $a + 8 |
** | 求幂,如 $a **3是$a求立方 |
. | 字符串连接,如$a="hi", $b="there", 则$a . $b = "hithere" |
x | 字符串重复,如 $string="hi"x3,字符串 $string为“hihihi” |
.= | 字符串拼接相连后再赋值 |
程序控制
程序的语句是按顺序执行的,即在继续执行下一个语句之前,前面的每个语句都会被完整执行。但是,有时可能需要更改执行流程,如你希望程序在发生一个事件时选择一套指令,并且响应不同事件时选择不同的指令。为实现这种能力,所有编程语言都提供程序控制的机制。Perl语言的程序控制一般是通过条件(conditional)语句和循环(loop)来实现这种选择。
条件执行
计算机程序经常需要一些在某些条件下才能执行的代码,而如果条件不符合,就跳过代码。几乎所有语言都使用if语句进行决策;决策包括条件测试、条件测试为真时执行的语句,以及条件测试为假时执行的语句。因此,if语句的基本结构如下伪代码所示:
if conditional expression
statement or block
else
statement or block
条件表达式是要计算的测试条件,例如,如果x>2条件为真,则执行if后面的语句或语句块。 与if语句关联的语句总是缩进以显示它们对条件表达式的依赖性。如果条件不为真,则应该执行备用语句,注意else块是可选的。 Perl语言还可以使用elsif允许多个其它条件,例如,我们可以使用elsif来测试x是否大于0并执行一些语句。还可以嵌套if语句,即在一个if语句中包含另一个if语句。 如以下Perl代码示例:
if ($x > 2){
print("x is large.\n”);
}elsif ($x > 0){
print("x is positive.\n”);
}else{
print("x is negative.\n”);
}
print("Done!\n”);
在这个例子中,第一行检验$x里存储的值是否比2大,如果是的话,第一个程序块被执行,并输出"x is large.”,然后程序跳到else块的结尾,并执行最后一个print语句,输出"Done!”。而如果$x比2小的话,则对第一个elsif进行判断,如果$x大于0,那么执行紧跟下面程序块,并输出"x is positive.”。 然后和前面一样,跳到else块的结尾,并执行最后一个print语句。如果$x不比0大,则执行最后的else语句块,输出“x is negative.”。if/else结构保证了可以根据$x值有且仅有一个条件程序块被执行。不管执行了哪个程序块,程序都会执行最后的print语句,并输出“Done!”。
这个例子只使用了简单的关系运算符”>”来判断变量的值是不是大于一个特定的数。Perl还有很多可以用于条件检验的关系与逻辑运算符(表2)。
表2 Perl常用的关系和逻辑运算符 |运算符|作用| |:----|:----| |<, >, <=, >=|数字比较大小| |lt, gt, le, ge|字符串按字母表顺序比较| |==, !=|数字等值| |eq, ne|字符串等值| |<=>|数字比较,返回1,0,-1| |cmp|字符串比较,返回1,0,-1| |&&, |||联合运算符,两个条件都为真或任何一个为真时返回真| |!|逻辑“非”|
注意每个比较运算符都有数字与字符串两个版本,它们之间是不一样的!它们间的差异可以用一个例子来说明。假设给两个变量赋值:
$var1 = “0012.0”;
$var2 = “12”;
这两个变量在数字上是相等的,因此检验($var1 == $var2)为TRUE,但这两个变量是不同的字符,所以检验($var1 eq $var2)为FALSE。 注意数字比较是要用两个等号(==),而不是一个等号(=),初学者容易犯这样的错误,写出下面的代码:
if ($var1 = $var2){
print (“the two variables are equal.\n”);
}
由于赋值语句$var1 = $var2是将$var2复制给$var1并返回TRUE(代表赋值成功)。这样不但$var1的值被意外也改变了,而且条件代码会永远执行。这样的错误可能很难被发现,所以要特别注意。
循环
当满足某些指定条件时,需要重复一段代码会使用循环。 使用循环时有三个要求:(1)必须有最终失败的测试条件(结束循环),(2)必须初始化测试条件,(3)测试条件必须改变。 这三个要求基于所使用的循环类型在不同位置发生。 Perl语言支持三种主要的循环类型:for,while和foreach。循环语句由条件测试和循环体组成。 程序运行时,先评估条件测试,如果为true,则循环体中的语句执行。 重复此过程直到条件测试失败。
一个while语句和if语句的结构类似:
$x = 0;
while ($x < 3) {
print ("The vale of x is: $x\n");
$x++;
}
这个例子中,while语句后面的程序块被执行3次,代码执行后在屏幕上显示以下输出结果:
The vale of x is: 0
The vale of x is: 1
The vale of x is: 2
for循环与while循环很相似,但for循环用于对一个程序块执行固定的次数。for循环语句通常包括一个变量,该变量在每次循环跳转时自动递增(或递减),但while循环语句必须在循环体中显式更改其变量。上面这个例子可以改成for循环:
for ($x = 0; $x < 3; $x++) {
print ("The vale of x is: $x\n");
}
注意for语句括号里的表达式由3部分组成,并用分号相隔。对于for和while循环,for和while循环首先执行条件测试,因此,循环体中的语句可能永远不会执行。上面例子中,如果第一行讲到”$x=4”的话,while后的代码块就不会被执行。如果需要至少执行一次循环体中的语句,可使用do-while循环语句,do-while循环是在循环体执行一次后,再执行条件测试。
Perl提供的另一个常用循环是foreach语句。它的功能与for循环很相似,但它对每一个列表(数组)中的值执行一次循环里的代码。下面是foreach的例子:
@arr = (7, 3, -3, 5, 2);
foreach $x @arr {
print ("The value is: $x\n");
}
这个循环会执行5次,数组里的每一个值都会执行一次。每次循环,列表中的下一值会被指定给变量$x。
数据结构
前面提到,变量将数据保存在内存中,而标量变量只能存储一条信息,如名称,电话号码,DNA序列等。但有时我们需要一个变量来处理一组相关数据,例如要存储一个矩阵数据,你可以为矩阵的每个元素创建一个标量变量,但太麻烦,如能让单个变量代表整个矩阵就要容易得多。 数据结构就是可以保存一个以上数据的变量。正确使用数据结构是编写效率高、容易阅读的代码的关键。
以下部分只简单介绍在编程语言中广泛使用的两种数据结构:数组(array)和哈希表(hash)。 请注意,这些数据结构的名称在不同的编程语言中可能不同,如在Python语言中,数组称为列表(list),而哈希表可以称为字典(dict)。
数组
数组用于保存多条相关信息。Perl的一个数组变量必需以符号@开头, 如一个名为firstNames的数组,其中包含名字“Jim”,“Sue”,“Bob”和“Alice”,可用如下方式创建:@firstNames = [“Jim”,“Sue”,“Bob”和“Alice”];
如果要访问数组中的特定单个元素,可通过中括号来访问:$firstNames[0], 下标符号用于表示数组中的特定元素,而元素以符号$起始是因为数组的每一个元素都是一个普通的标量。 要注意第一个元素总是编号为0,而不是1:$firstNames[0]表示名为firstNames数组中的第一个元素,而$firstNames[2]表示同一个数组中的第三个元素。最后一个元素的编号可用特殊符号"$#"表示,因此$firstNames[$#firstNames]表示数组中的最后一个元素。
其它构建数组的方法:
@numbers = (1 .. 20);
@fruits = qw (apple orange bananna);
这里"qw"意思是“quoted word”,另外界定符号括号()可以更换,如换成!!,//,{},<> 都可以。
Perl脚本不需要事先声明数组,在第一次访问数组中的元素时(如$numbers[0] = 20;),数组@numbers就会被创建了。注意要区别以下几个相似的变量名:
- $numbers – 包含一个值的标量变量
- @numbers – 包含多个值的一个数组变量numbers
- $numbers[2] – 数组变量numbers里的一个元素。
数组可以是单维(只有单行)或二维(具有行和列)的。如果数组是二维数组,则必须使用两个下标值指示行和列,因此$matrix[2,4]表示第三行和第五列中的元素(同样,下标从0开始)。数组元素个数可以用scalar函数或最后一个元素编号获得,如:
my $n = scalar (@firstNames);
my $n = $#firstNames + 1;
此时$n的值为4,即数组中有4个元素。
与数组相关的一些命令如下:
- shift: 从数组最左边去掉一个元素。
@arr = ("A", "B", "C"); $a = shift(@arr); # @arr=("B", "C"), $a="A"
- unshift: 从数组最左边加入一个元素。
@arr = ("B", "C"); unshift(@arr, "X"); # @arr=("X", "B", "C")
- pop: 从数组最右边去掉一个元素。
- push: 从数组最右边加入一个元素。
- splice: 从数组中去掉元素,命令格式为splice @array, n1, n2,代表从数组中编号n1的元素开始,向后去掉n2个元素。
splice @numbers, 2, 3;#从数组的第3个元素开始,去掉3个元素
- reverse: 将数组中的元素由后到前重新排列
@arr = (21, 1, 2, 12); @arr = reverse(@arr); # @arr=(12, 2, 1, 21)
- sort:将数组中的元素按ASCII码顺序由小到大排序。参数加上{$a<=>$b}之后按数字大小排序。=>
@arr = (21, 1, 2, 12); @arr = sort(@arr); # @arr=(1, 12, 2, 21) @arr = sort{$a<=>$b}(@arr); # @arr=(1, 2, 12, 21)
- join:用指定字符将数组中的元素连接成字符串。
@arr = ("A", "B", "C"); $str = join("-", @arr); # $str = "A-B-C"
- split: 把字符串按照pattern进行分割,并把分割后的结果放入数组中
$seq = "15:33:56"; @arr = split(/:/, $seq) #@arr=("15", "33", "56")
哈希表
与数组一样,哈希表允许使用单个变量名存储多个值。但是,在哈希表中,可以通过名称而不是数组中的数字来访问值,这对生物信息应用是非常有用的。如哈希表用于存储64种密码子及其编码的氨基酸,我们可以查找一个密码子并获得其对应的氨基酸。Perl的一个哈希变量必需以符号%开头,如哈希%aminoAcid的赋值如下:%aminoAcid = ( ‘UGG’ => ‘Trp’, ‘AUG’ => ‘Met’ );
哈希表中的条目由两个组件组成:键(key)和数据值(value)。键是用于访问(或查找)数据值的名称,因此只有在两组变量之间存在明确关系时才使用此结构。如这里的氨基酸哈希表%aminoAcid中,键“AUG”将与值“Met”相关联(或者使用氨基酸的单字母代码“M”)。哈希中的键值没有顺序,但是键在前,对应的值在后,为便于显示键值的配对关系,Perl用胖箭头(=>)连接键值。
我们可通过如下方式将精氨酸(“Arg”)与其对应的密码子“CGU”添加到%aminoAcid哈希表中:$aminoAcid{'CGU'} = 'Arg';
注意哈希表里的每个元素也是普通的标量变量,所以用名字前要加个$符号,但注意哈希表的索引是用大括号,而不是像数组一样用方括号。
我们也可通过如下方式将氨基酸“Trp”与其对应的密码子“UGG”从%aminoAcid哈希表中读取出来:print $aminoAcid{'UGG'};
使用哈希表的另一个例子是用于蛋白质比对的替换矩阵。在这里,我们需要同时查找两个氨基酸来确定一个分数,如当Ala被替换为Phe,与Ala被Gly替代的得分不同。 为了实现这一点,我们可以将一个哈希表嵌套在另一个哈希表中,因此,键“Ala”的值实际上是可以替代Ala的各种氨基酸值的整个哈希表。这使我们能够快速访问一对氨基酸的替换值。 在嵌套哈希表中存储值的代码如下所示:
$a1 = “Phe”;
$a2 = “Gly”;
$scoreTable[$a1, $a2] = 9;
与哈希相关的一些函数有:
keys: 取出哈希中的所有“键”。
my @key = keys %hash;
values: 取出哈希中的所有“值”。
my @value = values %hash;
each: 取出哈希中的所有“键”与其“值”。
while (my ($key,$value) = each %hash) { print "$key\n$value\n"; }
- exists: 检测哈希中某个键是否存在
if (exists %hash{key}) { print "$hash{key}\n"; }
- delete: 删除哈希元素
delete($hash{key});
字符串常用函数
- length,字符串长度
$str = "Perl5"; $len = length($str); print $len; # 字符串长度5
- substr,取子字符串
$str = "Perl5"; $s = substr($str, 2, 2); # $s="rl" $s = substr($str, -5, 3); # $s="per"
- index, 返回子字符串的位置
$str = "Perl5"; $s = index($str, 'P'); # $s=0 $s = index($str, "rl"); # $=2
函数(子程序)
当遇到一个大的问题时,往往要先将问题分解为小问题会有助于解决问题。同样,我们可能会有一些需要在一个程序中重复执行的代码,甚至可能需要在几个不同的程序中执行一些相同的代码。 例如,生物信息的许多程序需要将DNA序列转换成互补序列。 在这些情况下,可以使用函数 (function)。函数是程序执行主流程之外的一组语句。它可以从主程序中的语句调用,执行其语句,然后执行并返回结果于主程序(通常返回一些更改后的变量)。
如果你编写了一个函数来查找DNA链的互补序列,可在程序中多次调用它,每次都将给它传递不同的核苷酸序列。你也可以将该段代码复制到另一个程序中,因为它是一个函数,所以它不需要在主程序中进行更改。 因此,使用函数可使代码模块化。 实际上,你可以将程序视为由必需的主代码块和可选函数组成的模块。当程序执行时,执行主代码块并调用各个需要的可选函数。
函数由名字和指定为函数体的代码块组成。函数标题由函数名称和任何参数(调用例程传递给函数的信息)组成。 函数中使用return语句返回值,在有些编程语言中,还必须要指定返回数据的数据类型。 函数有自己的内存,因此参数和返回值用于在函数及其调用例程之间共享信息。
在Perl语言中函数也可称为子程序(subroutine)。子程序的定义以sub开始,伪代码的格式如下:
sub 函数名(参数1, 参数2, . . .){
函数体的代码;
}
下面看一个简单的子程序例子:
#!/usr/bin/perl
use strict;
#声明一个包含5元素的数组
my @value1 = (7, 5, 9, 12, -3);
#调用函数
Average(@value1);
#声明一个包含6元素的数组
my @value2 = (3, 9, 12, 3, -3, 5);
#调用函数
my $mean = Average(@value2);
print (“The mean value is : $mean\n”);
#===============================
# 定义求平均值函数
sub Average {
my $n = scalar(@_); #获取所有传入的参数个数
my $sum = 0;
foreach my $item (@_){ #求和
$sum += $item;
}
my $average = $sum / $n;
print ("传入的参数为 : @_\n"); # 打印整个数组
print ("第一个参数值为 : $_[0]\n"); # 打印第一个参数
print ("传入参数的平均值为 : $average\n"); # 打印平均值
return $average;
}
本例中sub Average这一行定义了子程序的名字为Average,其后是子程序的代码,包括在一对大括号内。子程序内的开头几行定义了一些局部变量(local variables),这些变量只作用于子程序的内部,并用关键字“my”声明。如果一个变量(如$n)在主程序或其它子程序中也存在相同名字的变量,那么它们与Average子程序中的变量$n不同。换言之,Average子程序中的变量$n的变化不会引起其它地方变量$n的改变。如果在子程序中创建变量时不使用my,那么它们就是全局变量(global variables)。全局变量在程序的各个地方都是可见的,在子程序中对其做的任何改变都会影响其它地方同样名字的变量。为了防止变量的意外改变,应该在子程序中使用局部变量,即声明变量时使用my,这是应该遵守的很好的编程习惯。Perl脚本前使用"use strict;"对没有使用my定义的变量将会发出警告。
调用子程序只需要使用子程序的名字,并把子程序的参数放在括号里。子程序的好处就是可以对不同的参数进行运算。Perl子程序的参数被存储在一个特殊的数组里(@),它的名字是简单的下划线()符号。这个数组的元素个数和调用子程序时传递过来的参数个数一样多。因为Perl里的数组是从0开始编码,所以第一个元素为$[0],而最后一个元素索引也可用一个特殊的符号($#)表示,因此所有参数的个数应该为$# + 1,或scalar(@)。
注意如果把一个数组作为一个参数,Perl将传递数组里的所有元素,就像是所有元素都在子程序后的括号里列出来一样。不管有多少参数传递给子程序,本例的foreach循环可以很方便处理参数。
子程序余下的部分就是计算平均值,并打印相关结果。Perl子程序也可以通过return语句返回一个值给调用它的主程序。这样当调用函数时,可以把返回值指派给一个变量,如下面的语句可以把子程序计算的平均值赋值给$mean变量:
my $mean = Average(@value2);
模块
获取下载网页为例:
use LWP::Simple; #引用模块
$url = 'http://www.uniprot.org/uniprot/Q6GV17.fasta';
$content = get $url; #获取网页内容
die "Couldn't get $url" unless defined $content; #如果网址无法打开,强制结束程序
print $content;
文件处理
计算机输入输出是指程序与计算机的交互过程。输入(或读数据)指将数据从键盘或外部存储设备(如存储在硬盘上的文件或仪器收集的数据)读取到内存中,其中键盘是标准输入设备。 相反,输出(或写数据)指将数据从主存储器移动到屏幕,文件或存储设备,其中屏幕是标准输出设备。传统计算机是没有屏幕的,输出设备主要是打印机。因此输出数据也习惯称为打印(print)数据。
从文件读取数据的语法类似于赋值语句语法,读入数据时,也必须指定保存数据的变量。下面先看一个Perl读取文件的例子:
#!/usr/bin/perl
use strict;
use warnings;
#打开文件并打印文件内容
open (FILENAME, "<test.txt");
open (FILEOUT, ">fileout.txt");
$row = 0; #统计文件的行数
while(<FILENAME>){ #每行读入的内容存储在了$_中
print ($_); #输出到屏幕
chomp; #删掉换行符,默认函数参数是$_
print FO "$_\n"; #输出到fileout.txt文件
$row++;
}
print $row; #输出行数
close(FILENAME);
close(FILEOUT);
在Perl里一个文件被读或写之前,它必须要先被打开,open函数用来打开文件。打开一个文件时就创建一个和文件关联的变量,通过读或写操作这个变量来操作文件。Perl使用一种称为文件句柄(file handle)的变量,它即不是标量、哈希或数组,所以不要用这些Perl变量关联的特殊符号($, %, @)作为起始。为了方便识别,文件句柄变量通常用大写字母的名字(如FILENAME)。Perl默认提供三种文件句柄:STDIN,STDOUT,STDERR,分别代表标准输入、标准输出和标准错误输出。
open函数的第二个参数是要打开的文件名。在文件名前加上“只读”符号(<),表示只希望对打开的文件进行读操作。同理,文件名前还可加入“只写”符号(>),表示把文件的已有内容清空,并从文件开头进行写;而加入“添加”符号(>>),表示保持文件的已有内容,从文件末尾进行写操作。如果原文件不存在,“>>”就会创建一个新文件,和Linux重定向方式一样。
从打开的文件句柄读取信息的主要方法是
chomp函数只去掉结尾的换行符,没有换行符就不起作用。
[!NOTE] 注意文本换行符在不同系统中有差异,Linux中是换行“\n”; macOS中是回车“\r”;Windows是回车加换行“\r\n”。 如果把windows系统中创建的文本文件复制到Linux系统,需要先转换成Linux换行符再使用chomp命令,不然使用chomp后还剩下符号"\r"。
另外,每当执行
最后的close()函数表示关闭文件读写。
屏幕的读写
在Linux系统中所有设备都是文件。屏幕是默认的输出输出设备,可以通过
print "What's your name?\n";
$name = <STDIN>; #输入完成后回车
chomp($name);
print "Hello $name\n";
正则表达式
Perl语言的正则表达式功能非常强大,是文本处理的利器。由于生物信息学的主要工作是序列分析,所以Perl语言得到广泛应用。本部分简单介绍Perl正则表达式的一些常用内容,更详细的内容请再参考其它学习资料[2]。
正则表达式(regular expression)就是一种字符串匹配的模式(pattern),可以用来检查一个字符串是否含有某子串或将匹配的子串替换等。模式一般用成对的符号表示,常用符号//,如“/pattern/”,也可以用其它符号##, (), ^^等。模式一般要和绑定运算符=~ 或 !~搭配使用(=~ 表示相匹配,!~ 表示不匹配)。=~后面是正则表达式,如:
$string =~ /abc/;
Perl的正则表达式有三种形式,分别是匹配,替换和转换:
- 模式匹配:m//
默认情况下,运算符m//(可以略去m,简写为//)尝试匹配指定的模式和$_中的文本,也可以用=~运算符指定查找的字符串。
如果查找匹配内容中包含“正则表达式内容”,则结果返回1,否则返回0。 正则表达式中一些字符具有特殊的意义, 有的是数量关系,如”?”匹配一个字符;”+”可匹配一个以上字符;而””可匹配0个或多个字符。如果要一个数字肯定出现,可写用[0-9]+,如要表明不能以0开始,可以用[1-9][0-9]。还有一个特殊通配符为句点(.),代表匹配任意字符(除换行符'\n'外),如要找alpha与beta两个词之间的任何字符,可以用正则表达式/alpha.*beta/。 有的有位置关系,如^与$,分别代表给定的模式必须在开始与结尾出现。/^alpha/只有当alpha在字符串的头部出现才匹配,而/alpha$/只有alpha在字符串的尾部才匹配。$sequence = “acgtabcgt”; $sequence =~ /abc/; #匹配字符串abc
正则表达式的中括号[]代表匹配其中的任何一个字符。如果想要匹配的不是一个特定的字符串,而是字符串的不同变形,如CAU或CAC,可以用/CA[CU]/,代表先匹配CA,后跟一个字符C或U。中括号[]中连续的字符串可以用连字符表示,如字符串[ABCDEabcdef],可以简写成[A-Ea-F]。 正则表达式的反斜杠("\")将下一个字符标记为一个特殊字符,如'd'匹配字符'd',而'\d'匹配任一个数字字符,等价于[0-9]。相反,'\D'匹配一个非数字字符。其它常见的有:(1) '\s'匹配任何空白字符,而'\S'匹配任何非空白字符;(2) '\w'匹配任何字母字符,而'\S'匹配任何非字母字符;(3) '\t'匹配一个制表符,'\n'为换行符等。
模式替换:s/// 运算符s///用一个字符串替换另一个字符串。
$sequence = “acgtabcgt”; $sequence =~ s/acgt/ACGT/; #将$sequence变为“ACGTabcgt”
查找匹配内容中是否包含“正则表达式内容”,如果包含,则使用“替换内容”替换,结果返回1,否则返回0。上句将”acgt”替换为”ACGT”,结果$sequence变为“ACGTabcgt”。
模式转换:tr/// 转换符tr///可用来换字(transliteration),即把匹配的一个字符换成其它字符,如将T换成U。
$sequence =~ tr/Tt/Uu/; #将字母T或t转换成U或u。
首先,“匹配内容集合”中元素会和“替换内容集合”中元素一一对应起来,然后执行匹配操作,匹配上的内容使用对应的替换集合中内容进行替换。注:功能同Linux命令tr。 另外tr转换符返回的是被转换字符的个数,因此还可以用以计算改变字符的次数。即使没有改变也可以计算次数,就是不要声明匹配的字符将要改成什么就行了。下面的例子计算一个序列中含有多少个U:$Ucount = ($sequence =~ tr/Uu//); printf(“There are $Ucount uracil in the the sequence\n”);
参考文献
- Randal L. Schwartz, Learning Perl (Perl编程入门), O’Reilly press, 2016
- Tore Samuelsson, Genomics and bioinformatics: an introduction to programming tools for life scientists. Cambridge University Press, 2012.
- Perl 程序员应该知道的事: http://perl.linuxtoy.org/