美文网首页Linux与生物信息
2小时30分钟 perl 速成

2小时30分钟 perl 速成

作者: Gravition | 来源:发表于2020-12-23 17:21 被阅读0次

perl

Learn Perl in about 2 hours 30 minutes

Practical Extraction and Report Language

Perl是一种动态的,动态类型的,高级脚本(解释)语言,类似于PHP和Python。
Perl的语法在很大程度上归功于古老的Shell脚本,并且因过度使用混乱的符号而闻名,大多数都无法Google 到。
Perl的shell脚本继承使其非常适合编写 glue 代码:将其他脚本和程序链接在一起的脚本。
Perl非常适合处理文本数据并生成更多文本数据。
Perl广泛,流行,高度可移植并且得到了良好的支持。
Perl的设计理念是 '条条大道通罗马'(TMTOWTDI)(与Python相比,'应该有一种,或者只有一种更好的方法')。

Perl令人恐惧,但它也具有一些很棒的补偿功能。在这方面,它就像过去创造的所有其他编程语言一样。

本文档旨在提供信息,而不是福音。它针对像我这样的人:

  • 不喜欢http://perl.org/上的Perl官方文档,因为它技术性很强,并且对不常见的边缘情况讲的太多
  • 通过公理和示例快速学习新的编程语言
  • 希望 Larry Wall 能讲到重点
  • 已经知道编程大概是什么
  • 除了完成工作所必需的之外,不太关心Perl

本文档旨在尽可能短,但不要更短。

初步说明

关于本文档中几乎每条陈述,都要附上:严格来说,这不是正确的;情况实际上要复杂得多
在整个文档中,我使用示例打印语句输出数据,但未明确附加换行符。这样做是为了防止我发疯,并更加关注每种情况下要打印的实际字符串,这总是更加重要。
在许多示例中,如果代码在现实中运行,则会导致大量的单词挤在一行中。尝试忽略这一点。

hello world

Perl 脚本的后缀名为.pl,helloworld.pl的全文是:

use strict;
use warnings;

print "Hello world";

Perl脚本由解释器perlperl.exe解释:

perl helloworld.pl [arg0 [arg1 [arg2 ...]]]

一些即时笔记。 Perl的语法是高度自由的。
避免它们的方法是在每个Perl脚本或模块的最顶部加上 use strict; use warnings;
use foo;这种形式称为pragmas。 它们是给perl.exe的信号,在程序开始运行之前执行初始语法验证时生效。 当解释器在运行时遇到它们时,这些行不起作用。
分号;是语句终止符。 符号开始注释。 注释一直持续到该行的末尾。 Perl没有块注释语法。

变量

Perl变量分为三种类型: scalar, arrays and hashes
每种类型都有自己的标志:分别为$,@
变量使用my声明,并一直保留到scope中,直到块封闭或文件结束。

scalar variable

标量变量可以包含:

  • undef (对应于Python中的None,PHP中的null)
  • 一个数字(Perl不区分整数和浮点数)
  • 一个字符串
  • 对任何其他变量的引用。
my $undef = undef;
print $undef; # prints the empty string "" and raises a warning

# implicit undef:
my $undef2;
print $undef2; # prints "" and raises exactly the same warning

my $num = 4040.5;
print $num; # "4040.5"

my $string = "world";
print $string; # "world"

使用.做字符串连接(与PHP相同):

print "Hello ".$string; # "Hello world"

booleans

Perl没有布尔数据类型。如果且仅当它是以下之一时,if语句中的标量求值为布尔值 false

  • undef
  • number 0
  • string ""
  • string "0"

Perl文档反复声明函数在某些情况下返回truefalse值。
实际上,当一个函数声称返回true时,通常返回1,而当一个函数声称返回false时,通常返回空字符串""

Weak typing

无法确定标量包含数字还是字符串。更确切地说,永远不需要这样做。
标量的行为像数字还是字符串取决于使用它的运算符。当用作字符串时,标量的行为类似于字符串。当用作数字时,标量的行为将类似于数字(如果不可能,则发出警告):

my $str1 = "4G";
my $str2 = "4H";

print $str1 .  $str2; # "4G4H"
print $str1 +  $str2; # "8" with two warnings
print $str1 eq $str2; # "" (empty string, i.e. false)
print $str1 == $str2; # "1" with two warnings

# The classic error
print "yes" == "no"; # "1" with two warnings; both values evaluate to 0 when used as numbers

须知是使用正确的运算符。将标量作为数字比较,与作为字符比较,有各自的运算符:

# Numerical operators:  <,  >, <=, >=, ==, !=, <=>, +, *
# String operators:    lt, gt, le, ge, eq, ne, cmp, ., x

array 变量

数组变量是由从0开始的整数索引的标量的列表。在Python中,这称为list,在PHP中,其称为array。使用带括号的标量列表声明数组:

my @array = (
    "print",
    "these",
    "strings",
    "out",
    "for",
    "me", # trailing comma is okay
);

您必须使用$符号来访问数组中的值,因为要检索的值不是数组,而是标量:

print $array[0]; # "print"
print $array[1]; # "these"
print $array[2]; # "strings"
print $array[3]; # "out"
print $array[4]; # "for"
print $array[5]; # "me"
print $array[6]; # returns undef, prints "" and raises a warning

您可以使用负数索引从尾部开始打印:

print $array[-1]; # "me"
print $array[-2]; # "for"
print $array[-3]; # "out"
print $array[-4]; # "strings"
print $array[-5]; # "these"
print $array[-6]; # "print"
print $array[-7]; # returns undef, prints "" and raises a warning

标量$var和包含标量$var [0]的数组@var之间没有冲突。但是,可能会使读者感到困惑,因此请避免这种情况。

获取数组的长度:

print "This array has ".(scalar @array)."elements"; # "This array has 6 elements"
print "The last populated index is ".$#array;       # "The last populated index is 5"

调用Perl脚本的参数存储在内置数组变量@ARGV中。

变量可以插入到字符串中:

print "Hello $string"; # "Hello world"
print "@array";        # "print these strings out for me"

注意,有时候你会需要将某人的电子邮件地址放入字符串jeff@gmail.com中。
这将导致Perl查找名为@gmail的数组变量以插值到字符串中,但找不到它,从而导致运行时错误。
可以通过两种方式来防止插值:通过反斜杠\对符号进行转义,或使用单引号而不是双引号。

print "Hello \$string"; # "Hello $string"
print 'Hello $string';  # "Hello $string"
print "\@array";        # "@array"
print '@array';         # "@array"

Hash变量

哈希变量是由字符串索引的标量的列表。在Python中,这被称为dictionary,而在PHP中,其被称为array

my %scientists = (
    "Newton"   => "Isaac",
    "Einstein" => "Albert",
    "Darwin"   => "Charles",
);

请注意,此声明与数组声明有多相似。实际上,双箭头符号=>被称为fat comma,因为它只是逗号分隔符的同义词。
使用具有偶数个元素的列表声明hash,其中偶数个元素(0,2,...)全部被当成字符串。

同样,你必须使用$符号从哈希中访问值,因为要检索出的值不是哈希,而是标量:

print $scientists{"Newton"};   # "Isaac"
print $scientists{"Einstein"}; # "Albert"
print $scientists{"Darwin"};   # "Charles"
print $scientists{"Dyson"};    # returns undef, prints "" and raises a warning

请注意此处使用的括号。同样,标量$var和包含标量条目$var {foo}的哈希%var`之间没有冲突。

你可以将哈希直接转换为具有两倍条目的数组,元素在键和值之间进行交替(逆过程也很容易):

my @scientists = %scientists;

但是与数组不同,哈希键没有基础顺序。它们将返回更有效率的顺序。因此,请注意在结果数组中顺序的重新排列,它们之保持成对:

print "@scientists"; # something like "Einstein Albert Darwin Charles Newton Isaac"

回顾一下,您必须使用方括号从数组中检索值,但必须使用花括号从哈希中检索值。
方括号实际上是数字运算符,而花括号实际上是字符串运算符。提供的索引是数字或字符串这一事实绝对没有意义:

my $data = "orange";
my @data = ("purple");
my %data = ( "0" => "blue");

print $data;      # "orange"
print $data[0];   # "purple"
print $data["0"]; # "purple"
print $data{0};   # "blue"
print $data{"0"}; # "blue"

lists

Perl中的列表不同于数组哈希。你刚刚看到了几个lists

(
    "print",
    "these",
    "strings",
    "out",
    "for",
    "me",
)
(
    "Newton"   => "Isaac",
    "Einstein" => "Albert",
    "Darwin"   => "Charles",
)

列表不是变量。列表是一个临时(ephemeral)vale,可以分配给数组或哈希变量。这就是为什么声明数组和哈希变量的语法相同的原因。
在许多情况下,术语listarray可以互换使用,但是在很多情况下,列表和数组的行为不同,令人感到混乱。

请记住,=>只是伪装的,`然后看这个例子:

("one", 1, "three", 3, "five", 5)
("one" => 1, "three" => 3, "five" => 5)

=>提示了列表之一是数组声明,而另一个是哈希声明。
但是就它们自己而言,它们都不是任何东西的声明。它们只是lists, identical lists:

()

这里甚至没有提示。
该列表可用于声明一个空数组或一个空哈希,并且perl解释器显然无法区分。
一旦了解了Perl这个奇怪的方面,你就理解了为什么:列表值不能嵌套。尝试一下:

my @array = (
    "apples",
    "bananas",
    (
        "inner",
        "list",
        "several",
        "entries",
    ),
    "cherries",
);

Perl无法知道("inner", "list", "several", "entries")应该是内部数组还是内部哈希。因此,Perl假定两者都不是,并将列表展平为单个长列表:

print $array[0]; # "apples"
print $array[1]; # "bananas"
print $array[2]; # "inner"
print $array[3]; # "list"
print $array[4]; # "several"
print $array[5]; # "entries"
print $array[6]; # "cherries"

是否使用fat comma都是同样的情况:

my %hash = (
    "beer" => "good",
    "bananas" => (
        "green"  => "wait",
        "yellow" => "eat",
    ),
);

# The above raises a warning because the hash was declared using a 7-element list

print $hash{"beer"};    # "good"
print $hash{"bananas"}; # "green"
print $hash{"wait"};    # "yellow";
print $hash{"eat"};     # undef, so prints "" and raises a warning

当然,这确实使连接多个数组起变得容易:

my @bones   = ("humerus", ("jaw", "skull"), "tibia");
my @fingers = ("thumb", "index", "middle", "ring", "little");
my @parts   = (@bones, @fingers, ("foot", "toes"), "eyeball", "knuckle");
print @parts;

不久之后会涉及更多。

Context

Perl 最独特的功能是其代码是上下文敏感的。 Perl中的每个表达式都是在标量上下文或列表上下文中求值的,具体取决于期望产生标量还是列表。
如果不了解表达式的上下文,就无法确定表达式的计算结果。

array-> 长度
list -> 末尾元素
reverser
scalar

标量分配my $scalar =在标量上下文中评估其表达式。在这里,表达式为"Mendeleev"

my $scalar = "Mendeleev";

数组或哈希分配(例如,my @array =my %hash =)在列表上下文中求值。
在此,表达式为("Alpha", "Beta", "Gamma", "Pie")("Alpha" => "Beta", "Gamma" => "Pie"),,两者等效:

my @array = ("Alpha", "Beta", "Gamma", "Pie");
my %hash = ("Alpha" => "Beta", "Gamma" => "Pie");

在列表上下文中求值的标量表达式将自动转换为单元素列表:

my @array = "Mendeleev"; # same as 'my @array = ("Mendeleev");'

在标量上下文中求值的array表达式返回array的长度:

my @array = ("Alpha", "Beta", "Gamma", "Pie");
my $scalar = @array;
print $scalar; # "4"

在标量上下文中求值的list表达式(list与数组不同,还记得吗?)不返回list的长度,而是返回list中的末位标量:

my $scalar = ("Alpha", "Beta", "Gamma", "Pie");
print $scalar; # "Pie"

print 内置函数在列表上下文中评估其所有参数。
实际上, print 接受无限数量的参数列表,并且一个接一个地打印,这意味着可以将其直接用于打印数组:

my @array = ("Alpha", "Beta", "Goo");
my $scalar = "-X-";
print @array;              # "AlphaBetaGoo";
print $scalar, @array, 98; # "-X-AlphaBetaGoo98";

警告。许多Perl表达式和内置函数根据它们的上下文表现出完全不同的行为。
最突出的例子是 function reverse。在列表上下文中,reverse将其参数视为列表,然后反转该列表。
在标量上下文中,reverse将整个列表连接在一起,然后将其作为单个单词反向。

print reverse "hello world"; # "hello world"

my $string = reverse "hello world";
print $string; # "dlrow olleh"

您可以使用scalar内置函数强制任何表达式在标量上下文中计算:

print scalar reverse "hello world"; # "dlrow olleh"

还记得我们之前如何使用scalar来获取数组的长度吗?

引用和嵌套数据结构

类似list不能包含list作为元素,arrayhash不能包含其他数组和hash作为元素。它们只能包含标量。观看当我们尝试时会发生什么:

my @outer = ("Sun", "Mercury", "Venus", undef, "Mars");
my @inner = ("Earth", "Moon");

$outer[3] = @inner;

print $outer[3]; # "2"

$outer [3]是一个scalar,因此它需要一个scalar值。当您尝试为其分配一个数组值(@inner)时,@innerscalar上下文中求值。这与分配saclar @inner相同,后者是array @inner的长度,即2

但是,scalar变量可以包含对任何变量的引用,包括array变量或哈希变量。这就是在Perl中创建更复杂的数据结构的方式。

引用使用反斜杠\创建。

my $colour    = "Indigo";
my $scalarRef = \$colour;

任何时候使用变量名时,都可以用引用代替,引用变量外加上一层braces

print $colour;         # "Indigo"
print $scalarRef;      # e.g. "SCALAR(0x182c180)"
print ${ $scalarRef }; # "Indigo"

只要结果没有歧义,您也可以省略花括号:

print $$scalarRef; # "Indigo"

如果您的引用是对数组或哈希变量的引用,则可以使用花括号或使用更流行的箭头运算符->从其中获取数据。

my @colours = ("Red", "Orange", "Yellow", "Green", "Blue");
my $arrayRef = \@colours;

print $colours[0];       # direct array access
print ${ $arrayRef }[0]; # use the reference to get to the array
print $arrayRef->[0];    # exactly the same thing

my %atomicWeights = ("Hydrogen" => 1.008, "Helium" => 4.003, "Manganese" => 54.94);
my $hashRef = \%atomicWeights;

print $atomicWeights{"Helium"}; # direct hash access
print ${ $hashRef }{"Helium"};  # use a reference to get to the hash
print $hashRef->{"Helium"};     # exactly the same thing - this is very common

声明数据结构

这里有四个示例,但实际上最后一个是最有用的。

my %owner1 = (
    "name" => "Santa Claus",
    "DOB"  => "1882-12-25",
);

my $owner1Ref = \%owner1;

my %owner2 = (
    "name" => "Mickey Mouse",
    "DOB"  => "1928-11-18",
);

my $owner2Ref = \%owner2;

my @owners = ( $owner1Ref, $owner2Ref );

my $ownersRef = \@owners;

my %account = (
    "number" => "12345678",
    "opened" => "2000-01-01",
    "owners" => $ownersRef,
);

这显然是不必要的麻烦,因为您可以将其缩短为:

my %owner1 = (
    "name" => "Santa Claus",
    "DOB"  => "1882-12-25",
);

my %owner2 = (
    "name" => "Mickey Mouse",
    "DOB"  => "1928-11-18",
);

my @owners = ( \%owner1, \%owner2 );

my %account = (
    "number" => "12345678",
    "opened" => "2000-01-01",
    "owners" => \@owners,
);

也可以使用不同的符号声明匿名数组和哈希。使用方括号声明匿名array,使用圆括号声明匿名array
在每种情况下返回的值都是对所讨论的匿名数据结构的引用。请仔细观察,其结果是与上面的%account完全相同:

# Braces denote an anonymous hash
my $owner1Ref = {
    "name" => "Santa Claus",
    "DOB"  => "1882-12-25",
};

my $owner2Ref = {
    "name" => "Mickey Mouse",
    "DOB"  => "1928-11-18",
};

# Square brackets denote an anonymous array
my $ownersRef = [ $owner1Ref, $owner2Ref ];

my %account = (
    "number" => "12345678",
    "opened" => "2000-01-01",
    "owners" => $ownersRef,
);

或者,简而言之(这是 in-line 声明复杂数据结构时,实际中应该使用的形式):

my %account = (
    "number" => "31415926",
    "opened" => "3000-01-01",
    "owners" => [
        {
            "name" => "Philip Fry",
            "DOB"  => "1974-08-06",
        },
        {
            "name" => "Hubert Farnsworth",
            "DOB"  => "2841-04-09",
        },
    ],
);

从数据结构中获取信息

现在,让我们假设您还有%account,但是其他所有内容(如果有其他内容)都超出了范围。
您可以通过在每种情况下相反的步骤来打印信息。同样,这里有四个示例,其中最后一个是最有用的:

my $ownersRef = $account{"owners"};
my @owners    = @{ $ownersRef };
my $owner1Ref = $owners[0];
my %owner1    = %{ $owner1Ref };
my $owner2Ref = $owners[1];
my %owner2    = %{ $owner2Ref };
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或者,简而言之:

my @owners = @{ $account{"owners"} };
my %owner1 = %{ $owners[0] };
my %owner2 = %{ $owners[1] };
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或使用引用和->运算符:

my $ownersRef = $account{"owners"};
my $owner1Ref = $ownersRef->[0];
my $owner2Ref = $ownersRef->[1];
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1Ref->{"name"}, " (born ", $owner1Ref->{"DOB"}, ")\n";
print "\t", $owner2Ref->{"name"}, " (born ", $owner2Ref->{"DOB"}, ")\n";

或者跳过所有中间取值

print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $account{"owners"}->[0]->{"name"}, " (born ", $account{"owners"}->[0]->{"DOB"}, ")\n";
print "\t", $account{"owners"}->[1]->{"name"}, " (born ", $account{"owners"}->[1]->{"DOB"}, ")\n";

如何使用数组引用让自己快乐

该数组包含五个元素:

my @array1 = (1, 2, 3, 4, 5);
print @array1; # "12345"

但是,此数组具有单个元素(它是对匿名的五元素数组的引用):

my @array2 = [1, 2, 3, 4, 5];
print @array2; # e.g. "ARRAY(0x182c180)"

scalar是对一个匿名的五元素数组的引用:

my $array3Ref = [1, 2, 3, 4, 5];
print $array3Ref;      # e.g. "ARRAY(0x22710c0)"
print @{ $array3Ref }; # "12345"
print @$array3Ref;     # "12345"

Conditionals

if ... elsif ... else ...

除了elsif的拼写,这里没有其他惊喜:

my $word = "antidisestablishmentarianism";
my $strlen = length $word;

if($strlen >= 15) {
    print "'", $word, "' is a very long word";
} elsif(10 <= $strlen && $strlen < 15) {
    print "'", $word, "' is a medium-length word";
} else {
    print "'", $word, "' is a short word";
}

Perl提供了一个较短的statement if condition语法,强烈建议在短语句中使用:

print "'", $word, "' is actually enormous" if $strlen >= 20;

unless ... else ...

我的$温度= 20;

my $temperature = 20;

unless($temperature > 30) {
    print $temperature, " degrees Celsius is not very hot";
} else {
    print $temperature, " degrees Celsius is actually pretty hot";
}

最好避免使用unless,因为它们会造成混乱。可以通过否定条件(或通过保留条件并交换这些块)来将"unless [... else]"微不足道地重构为if [... else]块。
幸运的是,没有elsunless关键字。

相比之下,强烈建议您这样做,因为它很容易阅读:

print "Oh no it's too cold" unless $temperature > 15;

Ternary 三元运算符

三元运算符?:允许将简单的if语句嵌入到一条语句中。一个标准用法是单数/复数形式:

my $gain = 48;
print "You gained ", $gain, " ", ($gain == 1 ? "experience point" : "experience points"), "!";

旁白:在两种情况下,最好将单数和复数完整地拼写。不要做下面这种奇怪的事情,因为任何人搜索代码库来替换单词toothteeth时,都找不到此行:

my $lost = 1;
print "You lost ", $lost, " t", ($lost == 1 ? "oo" : "ee"), "th!";

三元运算符可以嵌套:

my $eggs = 5;
print "You have ", $eggs == 0 ? "no eggs" :
                   $eggs == 1 ? "an egg"  :
                   "some eggs";

if语句在标量上下文中计算其条件。
例如,当且仅当@array具有1个或多个元素时,if(@array)返回true
这些元素是什么无关紧要-对于我们关心的所有元素,它们可能包含undef或其他false

循环

有不止一种方法可以做到这一点。
Perl有一个常规的while循环:

my $i = 0;
while($i < scalar @array) {
    print $i, ": ", $array[$i];
    $i++;
}

Perl还提供了until关键字:

my $i = 0;
until($i >= scalar @array) {
    print $i, ": ", $array[$i];
    $i++;
}

这些do循环与上面的几乎等效(如果@array为空,则会发出警告):

my $i = 0;
do {
    print $i, ": ", $array[$i];
    $i++;
} while ($i < scalar @array);

and

my $i = 0;
do {
    print $i, ": ", $array[$i];
    $i++;
} until ($i >= scalar @array);

也可以使用基本的C风格的循环。注意我们如何在for语句中放入my,仅在循环范围内声明$i

for(my $i = 0; $i < scalar @array; $i++) {
    print $i, ": ", $array[$i];
}
# $i has ceased to exist here, which is much tidier.

这种for循环被认为是老式的,应尽可能避免。list 上的原生迭代要好得多。
注意:与PHP不同,forforeach关键字是同义词。只需选用最容易理解的即可:

foreach my $string ( @array ) {
    print $string;
}

如果确实需要索引,则range operator ..将创建一个匿名整数列表:

foreach my $i ( 0 .. $#array ) {
    print $i, ": ", $array[$i];
}

您不能遍历哈希。但是,您可以遍历其键。使用keys内置函数检索包含哈希的所有键的array。然后对array使用foreach方法:

foreach my $key (keys %scientists) {
    print $key, ": ", $scientists{$key};
}

由于哈希没有基础顺序,因此可以按任何顺序返回键。使用内置的sort函数可以按字母顺序对键数组进行排序:

foreach my $key (sort keys %scientists) {
    print $key, ": ", $scientists{$key};
}

如果您不提供显式的迭代器,则Perl使用默认的迭代器$ _$_是第一个也是最友好的内置变量:

foreach ( @array ) {
    print $_;
}

如果使用默认的迭代器,而您只希望在循环中放入一条语句,则可以使用超短循环语法:

print $_ foreach @array;

Loop control

nextlast可以用来控制循环的进度。在大多数编程语言中,它们分别称为continuebreak
我们还可以选择为任何循环提供label。按照惯例,标签全部以大写书写。
在标记了循环之后,next and last可以定位到该标记。本示例查找低于100的素数:

CANDIDATE: for my $candidate ( 2 .. 100 ) {
    for my $divisor ( 2 .. sqrt $candidate ) {
        next CANDIDATE if $candidate % $divisor == 0;
    }
    print $candidate." is prime\n";
}

Array functions

in-place array modification

我们将使用@stack演示这些:

my @stack = ("Fred", "Eileen", "Denise", "Charlie");
print @stack; # "FredEileenDeniseCharlie"

pop提取并返回数组的最后一个元素。这可以认为是堆栈的顶部:

print pop @stack; # "Charlie"
print @stack;     # "FredEileenDenise"

push将多余的元素追加到数组的末尾:

push @stack, "Bob", "Alice";
print @stack; # "FredEileenDeniseBobAlice"

shift提取并返回数组的第一个元素:

print shift @stack; # "Fred"
print @stack;       # "EileenDeniseBobAlice"

unshift在数组的开头插入新元素:

unshift @stack, "Hank", "Grace";
print @stack; # "HankGraceEileenDeniseBobAlice"

pop,push,shiftunshift 都是splice的特例。splice删除并返回一个数组切片,将其替换为另一个数组切片:

print splice(@stack, 1, 4, "<<<", ">>>"); # "GraceEileenDeniseBob"
print @stack;                             # "Hank<<<>>>Alice"

从旧数组创建新数组

Perl提供了以下功能,这些功能可作用于数组以创建其他数组。

join函数将许多字符串合并为一个:

my @elements = ("Antimony", "Arsenic", "Aluminum", "Selenium");
print @elements;             # "AntimonyArsenicAluminumSelenium"
print "@elements";           # "Antimony Arsenic Aluminum Selenium"
print join(", ", @elements); # "Antimony, Arsenic, Aluminum, Selenium"

list上下文中,reverse函数以相反的顺序返回列表。在标量上下文中,reverse将整个列表连接在一起,然后将其作为单个单词反向。

print reverse("Hello", "World");        # "WorldHello"
print reverse("HelloWorld");            # "HelloWorld"
print scalar reverse("HelloWorld");     # "dlroWolleH"
print scalar reverse("Hello", "World"); # "dlroWolleH"

map函数将一个数组作为输入,并对该数组中的每个标量$_应用一个运算。然后,它根据结果构造一个新的数组。大括号内的单个表达式提供了要执行的操作:

my @capitals = ("Baton Rouge", "Indianapolis", "Columbus", "Montgomery", "Helena", "Denver", "Boise");

print join ", ", map { uc $_ } @capitals;
# "BATON ROUGE, INDIANAPOLIS, COLUMBUS, MONTGOMERY, HELENA, DENVER, BOISE"

grep函数将一个数组作为输入,并返回一个经过过滤的数组作为输出, 语法类似于map
它将计算第二个参数中的每个标量$_。如果返回布尔值true,则将标量放入输出数组,否则不放入。

print join ", ", grep { length $_ == 6 } @capitals;
# "Helena, Denver"

显然,结果数组的长度就是成功匹配的次数,这意味着您可以使用grep快速检查数组是否包含元素:

print scalar grep { $_ eq "Columbus" } @capitals; # "1"

grepmap 可以结合起来形成 list comprehensions,这是许多其他编程语言所不具备的非常强大的功能。

默认情况下,sort函数返回输入数组,并按词汇(字母顺序)顺序排序:

my @elevations = (19, 1, 2, 100, 3, 98, 100, 1056);
print join ", ", sort @elevations;
# "1, 100, 100, 1056, 19, 2, 3, 98"

但是,类似于grepmap,您可以提供一些自己的代码。
排序总是通过在两个元素之间使用一系列比较完成的,你的块接收$a$b作为输入,如果$a小于$b,则返回-1;如果它们相等,则返回0;如果$a大于$b,则返回1

cmp运算符就可以实现此操作:

print join ", ", sort { $a cmp $b } @elevations;
# "1, 100, 100, 1056, 19, 2, 3, 98"

"spaceship operator" <=>对数字的作用相同:

print join ", ", sort { $a <=> $b } @elevations;
# "1, 2, 3, 19, 98, 100, 100, 1056"

$a$b始终是标量,但是它们可以引用很难比较的相当复杂的对象。如果需要更多空间进行比较,则可以创建一个单独的子例程并提供其名称,而不是:

sub comparator {
    # lots of code...
    # return -1, 0 or 1
}

print join ", ", sort comparator @elevations;

对于grepmap操作,您无法执行此操作。

注意,永远不会显式地提供$a$bsubroutineblock。像$_一样,$a$b实际上是全局变量,每次比较时,给它们填充一对值。

Built-in 函数

到目前为止,您至少已经看到了十二种内置函数: print, sort, map, grep, keys, scalar 等。内置功能是Perl的最大优势之一。他们

  • 很多
  • 非常有用
  • 文档很多
  • 语法差异很大,因此请查看文档
  • 有时接受正则表达式作为参数
  • 有时接受整个代码块作为参数
  • 有时参数之间不需要逗号
  • 有时会使用任意数量的逗号分隔的参数,有时不会
  • 如果提供的参数太少,有时会填写自己的参数
  • 一般情况下,除非模棱两可的情况,否则通常不需要在参数周围加上方括号

关于内置功能的最佳建议是know that they exist。浏览文档以备将来参考。如果您正在执行的任务看起来像是已经完成了很多次的低级通用任务,那么很有可能已经完成了。

用户定义的子例程

子例程使用sub关键字声明。
与内置函数相反,用户定义的子例程始终接受相同的输入:标量列表。
该列表当然可以只有一个元素,也可以是空的。单个标量被当成单个元素的列表。具有N个元素的hash被视为具有2N个元素的列表。

尽管brackets是可选的,但子例程应始终使用bracket来调用,即使没有参数的时候。这清楚地表明正在调用子例程。

进入子例程后,可通过built-in array variable @_使用自变量。例:

sub hyphenate {

  # Extract the first argument from the array, ignore everything else
  my $word = shift @_;

  # An overly clever list comprehension
  $word = join "-", map { substr $word, $_, 1 } (0 .. (length $word) - 1);
  return $word;
}

print hyphenate("exterminate"); # "e-x-t-e-r-m-i-n-a-t-e"

Perl通过引用进行调用

与几乎所有其他主流编程语言不同,Perl通过reference进行调用。
这意味着子例程主体内部可用的变量或值不是originals的副本。它们are the originals

my $x = 7;

sub reassign {
  $_[0] = 42;
}

reassign($x);
print $x; # "42"

如果您尝试类似

reassign(8);

那么就会发生错误并停止执行,因为reassign()的第一行等于

8 = 42;

这河里吗?

要学习的是,在子例程的主体中,在使用参数之前,应该先对它们进行解包(unpack)。

解包参数

解压缩@_的方式不止一种,但有些方法更好。

下面的示例子例程left_pad用指定的填充字符将字符串填充到所需的长度。(x函数将一个字符串拓展到n倍)
(注意:为简便起见,这些子例程都缺少一些基本的错误检查,即确保填充字符长度为1,并检查宽度是否大于等于现有字符串的长度,以及检查所有必需的参数是否齐全)

left_pad通常按以下方式调用:

print left_pad("hello", 10, "+"); # "+++++hello"
  • 逐项解包@_是有效的,但并不是很漂亮:
sub left_pad {
    my $oldString = $_[0];
    my $width     = $_[1];
    my $padChar   = $_[2];
    my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    return $newString;
}
  • 4个参数以下的情况,建议使用shift(移位)来解包@_
sub left_pad {
    my $oldString = shift @_;
    my $width     = shift @_;
    my $padChar   = shift @_;
    my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    return $newString;
}

如果没有为shift函数提供数组,则它将隐式对@_进行操作。这种方法很常见:

sub left_pad {
    my $oldString = shift;
    my $width     = shift;
    my $padChar   = shift;
    my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    return $newString;
}

超过4个参数时,很难跟踪萝卜插在哪个坑。

  1. 你可以使用多标量赋值,一次性解包@_。同样,最好在4个参数以下:
sub left_pad {
    my ($oldString, $width, $padChar) = @_;
    my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    return $newString;
}

对于具有大量参数的子例程,或其中某些参数是可选的或不能与其他参数组合使用的子例程,
最佳做法是要求用户在调用子例程时提供参数的hash,然后将@_解包为参数的hash
使用这种方法,我们的子例程调用看起来会有所不同:

print left_pad("oldString" => "pod", "width" => 10, "padChar" => "+");

子例程本身看起来像这样:

sub left_pad {
    my %args = @_;
    my $newString = ($args{"padChar"} x ($args{"width"} - length $args{"oldString"})) . $args{"oldString"};
    return $newString;
}

返回值

像其他Perl表达式一样,子例程调用可以展示上下文行为。
您可以使用wantarray函数(应该叫做wantlist,算了别管了)来检测子例程所处的上下文,并返回适合该上下文的结果:

sub contextualSubroutine {
    # Caller wants a list. Return a list
    return ("Everest", "K2", "Etna", "\n") if wantarray;

    # Caller wants a scalar. Return a scalar
    return 3 ."\n";
}

my @array = contextualSubroutine();
print @array; # "EverestK2Etna"

my $scalar = contextualSubroutine();
print $scalar; # "3"

System calls

每次在Windows或Linux系统上完成进程时,它会以一个16位status word结束(并且我假设在大多数其他系统上也成立)。
最高的8位构成一个介于0255之间(含0255)的return code,其中0通常表示未验证的运行成功,
而其他值则表示不同程度的失败。其他8的出场频率较低-它们"reflect mode of failure, like signal death and core dump information"(核心转储信息)。

你可以在退出Perl脚本时,使用exit选择返回码(0255)中。

Perl提供了More Than One Way To在调用中生成子进程,然后暂停当前脚本的执行,直到该子进程完成,再恢复对当前脚本的解释。
无论使用哪种方法, 子进程执行之后,都将立即发现内置标量变量$?包含了子进程的status word(16位)。
您可以只取这16位中的最高8位来获取return code$? >> 8

system函数可用于,使用给定参数调用其他程序。system返回的值与$?相同:

my $rc = system "perl", "anotherscript.pl", "foo", "bar", "baz";
$rc >>= 8;
print $rc; # "37"

或者,您可以使用反引号 ``在命令行上运行实际命令并捕获该命令的标准输出。
在标量上下文中,整个输出作为单个字符串返回。
在列表上下文中,整个输出以字符串数组的形式返回,每个字符串代表一行输出。

my $text = `perl anotherscript.pl foo bar baz`;
print $text; # "foobarbaz"

anotherscript.pl代码示例:

use strict;
use warnings;

print @ARGV;
exit 37;

文件和文件句柄

除了数字/字符串/引用或undef, 标量变量也可以包含file handle(文件句柄)。
文件句柄本质上是对特定文件中特定位置的引用。

使用open将标量变量转换为文件句柄。 open必须提供一个mode。模式<表示我们希望打开文件并读取它:

my $f = "text.txt";
my $result = open my $fh, "<", $f;

if(!$result) {
    die "Couldn't open '".$f."' for reading because: ".$!;
}

如果成功,则open返回一个真值。
否则,它返回false并将错误消息填充到内置变量$!中。如上所示,您应始终检查打开操作是否成功完成。这种检查非常繁琐,一个常见的习惯用法是:

open(my $fh, "<", $f) || die "Couldn't open <".$f."> for reading because: ".$!;

请注意,在打开调用的参数周围需要括号()

要从文件句柄读取一行文本,请使用readline内置函数。
readline返回一整行文本,并在其末尾保留换行符(文件的最后一行可能除外),如果到达文件末尾,则为undef

while(1) {
    my $line = readline $fh;
    last unless defined $line;
    # process the line...
}

要截断可能的trailing(尾随)换行符,请使用chomp(啃):

chomp $line;

Note that chomp acts on $line in place。 $line = chomp $line可能不是您想要的。

您也可以使用 eof 来检测是否已到达文件末尾:

while(!eof $fh) {
    my $line = readline $fh;
    # process $line...
}

但是请注意,仅使用while(my $line = readline $fh),因为如果$line最终为0,则循环将提前终止。
如果您想编写类似的内容,Perl提供了<>运算符,该运算符以一种较为安全的方式包装了readline。这是很常见且非常安全的:

while(my $line = <$fh>) {
    # process $line...
}

乃至:

while(<$fh>) {
    # process $_...
}

写入文件涉及首先以其他模式打开文件。模式>表示我们希望打开文件进行写入。
>将破坏目标文件的内容, 如果目标文件已经存在并且具有内容。要仅附加到现有文件,请使用模式>>。然后,只需将 filehandle 作为 print 函数的第 0 个参数即可。

open(my $fh2, ">", $f) || die "Couldn't open '".$f."' for writing because: ".$!;
print $fh2 "The eagles have left the nest";

请注意,$fh2和下一个参数之间没有逗号。

实际上,文件句柄超出范围时会自动关闭,否则应该使用:

close $fh2;
close $fh;

存在三个文件句柄作为global常量:STDINSTDOUTSTDERR。这些在脚本启动时自动打开。读取一行用户输入:

my $line = <STDIN>;

要仅等待用户按下Enter键:

<STDIN>;

没有文件句柄的调用<>将从STDIN,或调用Perl脚本时从参数命名的任何文件中读取数据。

正如您可能已经注意到的,如果未给出文件句柄,则默认情况下print打印输出到STDOUT

文件测试

函数-e是一个内置函数,用于测试文件是否存在。

print "what" unless -e "/usr/bin/perl";

函数-d是一个内置函数,用于测试文件是否为目录。

函数-f是一个内置函数,用于测试命名文件是否为纯文件。

这些只是-X形式的一大类函数中的三个,其中X是一些小写或大写字母。这些功能称为file tests。注意前面的减号。
在Google查询中,减号表示排除包含该搜索词的结果。这使得Googlefile tests 比较困难。只需搜索perl file tests”即可。

正则表达式

正则表达式在Perl之外的许多语言和工具中都有。
Perl的核心正则表达式语法与其他地方基本相同,但是Perl的完整正则表达式功能极其复杂且难以理解。
我能给您的最好建议是,尽可能避免这种复杂性。

匹配操作使用=~ m//进行。在标量上下文中,=~ m//成功则返回true,失败则返回false

my $string = "Hello world";
if($string =~ m/(\w+)\s+(\w+)/) {
    print "success";
}

括号执行sub-matches。执行成功的匹配操作后,子匹配将填充到内置变量$1, $2, $3, ...中:

print $1; # "Hello"
print $2; # "world"

list上下文中,=~ m//返回$1, $2...的列表。

my $string = "colourless green ideas sleep furiously";
my @matches = $string =~ m/(\w+)\s+((\w+)\s+(\w+))\s+(\w+)\s+(\w+)/;
print join ", ", map { "'".$_."'" } @matches;
# prints "'colourless', 'green ideas', 'green', 'ideas', 'sleep', 'furiously'"

替换操作使用=~ s///进行。

my $string = "Good morning world";
$string =~ s/world/Vietnam/;
print $string; # "Good morning Vietnam"

注意$string的内容如何改变。您必须在=~ s///操作的左侧传递一个标量变量。如果传递 literal 字符串,则会出现错误。

/g flag 表示"全局匹配"。

在标量上下文中,=~ m//g调用将在都在前一个匹配之后寻找后一个匹配,成功则返回true,失败则返回false
之后,您可以按照通常的方式访问$1,依此类推。例如:

my $string = "a tonne of feathers or a tonne of bricks";
while($string =~ m/(\w+)/g) {
  print "'".$1."'\n";
}

list上下文中,=~ m//g调用一次返回所有匹配项。

my @matches = $string =~ m/(\w+)/g;
print join ", ", map { "'".$_."'" } @matches;

=~ s///g调用执行全局搜索/替换并返回匹配的数目。在这里,我们将所有元音都替换为字母"r"

# 不带 /g 使用.
$string =~ s/[aeiou]/r/;
print $string,"\n"; # "r tonne of feathers or a tonne of bricks"

# 再次使用
$string =~ s/[aeiou]/r/;
print $string,"\n"; # "r trnne of feathers or a tonne of bricks"

# 使用 /g 处理所有剩余匹配
$string =~ s/[aeiou]/r/g;
print $string,"\n"; # "r trnnr rf frrthrrs rr r trnnr rf brrcks"

/i标志使匹配和替换不区分大小写。

/x标志允许您的正则表达式包含空格(例如换行符)和注释。

"Hello world" =~ m/
  (\w+) # 单个或者更多字母
  [ ]   #单个 literal 空白, 写在一个字符类[]中
  world # literal "world"
/x;
# 返回true

相关文章

网友评论

    本文标题:2小时30分钟 perl 速成

    本文链接:https://www.haomeiwen.com/subject/kxrunktx.html