内容

名称

perlintro - Perl 简介和概述

描述

本文档旨在为您提供 Perl 编程语言的快速概述,以及指向更多文档的指针。它旨在作为 Perl 新手的“引导”指南,提供足够的信息,让您能够阅读其他人的 Perl 代码并大致了解其功能,或者编写自己的简单脚本。

本入门文档并非旨在完整。它甚至不旨在完全准确。在某些情况下,为了传达总体思路,牺牲了完美。强烈建议您在阅读本入门文档后,从完整的 Perl 手册中获取更多信息,其目录可以在 perltoc 中找到。

在本文档中,您会看到对 Perl 文档其他部分的引用。您可以使用 perldoc 命令或您用来阅读本文档的任何方法来阅读这些文档。

在 Perl 的文档中,您会发现许多旨在帮助解释所讨论功能的示例。请记住,其中许多是代码片段,而不是完整的程序。

这些示例通常反映了该部分文档作者的风格和偏好,并且可能比实际程序中的相应代码行更简洁。除非另有说明,否则您应该假设 use strictuse warnings 语句出现在“程序”的前面,并且使用的任何变量都已声明,即使这些声明已被省略以使示例更易于阅读。

请注意,这些示例是由许多不同的作者在几十年的时间里编写的。因此,风格和技术会有所不同,尽管我们已经尽力在同一部分中不使风格差异过大。不要认为一种风格比另一种更好 - “有多种方法可以做到”是 Perl 的座右铭之一。毕竟,在您作为程序员的旅程中,您可能会遇到不同的风格。

什么是 Perl?

Perl 是一种通用编程语言,最初是为文本处理而开发的,现在被用于各种任务,包括系统管理、Web 开发、网络编程、GUI 开发等等。

该语言旨在实用(易于使用、高效、完整),而不是美观(小巧、优雅、简洁)。它的主要特点是易于使用,支持过程式和面向对象(OO)编程,具有强大的内置文本处理支持,并拥有世界上最令人印象深刻的第三方模块集合之一。

perlperlfaq1 以及其他地方给出了 Perl 的不同定义。由此我们可以确定,Perl 对不同的人来说意味着不同的事情,但很多人认为它至少值得写一写。

运行 Perl 程序

要从 Unix 命令行运行 Perl 程序

perl progname.pl

或者,将此作为脚本的第一行

#!/usr/bin/env perl

... 并以 /path/to/script.pl 的方式运行脚本。当然,它需要先具有可执行权限,因此 chmod 755 script.pl(在 Unix 下)。

(此起始行假设您拥有 env 程序。您也可以直接将路径放到您的 perl 可执行文件,例如在 #!/usr/bin/perl 中)。

有关更多信息,包括其他平台(如 Windows)的说明,请阅读 perlrun

安全网

默认情况下,Perl 非常宽容。为了使其更健壮,建议在每个程序的开头添加以下几行

#!/usr/bin/perl
use strict;
use warnings;

这两行额外的代码行是来自 perl 的请求,用于捕获代码中各种常见问题。它们检查不同的内容,因此您需要两者。由 use strict; 捕获的潜在问题会在遇到时立即导致代码停止运行,而 use warnings; 仅会发出警告(类似于命令行开关 -w)并让您的代码继续运行。要了解更多信息,请查看它们各自的手册页:strictwarnings

use v5.35(或更高版本)声明将同时启用 strictwarnings

#!/usr/bin/perl
use v5.35;

除了启用 strictwarnings 语言特性之外,此声明还会激活一个 "特性包";一个包含命名特性的集合,这些特性启用了许多最近对语言的添加和更改,有时还会删除发现设计错误且不鼓励使用的旧特性。

基本语法概述

Perl 脚本或程序由一个或多个语句组成。这些语句只需以直接的方式写入脚本中即可。无需使用 main() 函数或任何类似的东西。

Perl 语句以分号结尾

print "Hello, world";

注释以井号开头,一直持续到行尾

# This is a comment

空白字符无关紧要

print
    "Hello, world"
    ;

... 除了在引号字符串中

# this would print with a linebreak in the middle
print "Hello
world";

双引号或单引号可用于围绕字面字符串

print "Hello, world";
print 'Hello, world';

但是,只有双引号会 "插值" 变量和特殊字符,例如换行符 (\n)

print "Hello, $name\n";     # works fine
print 'Hello, $name\n';     # prints $name\n literally

数字不需要用引号括起来

print 42;

您可以使用圆括号来表示函数的参数,也可以根据个人喜好省略它们。它们只有在需要澄清优先级问题时才需要。

print("Hello, world\n");
print "Hello, world\n";

有关 Perl 语法的更详细的信息,请参阅 perlsyn

Perl 变量类型

Perl 有三种主要的变量类型:标量、数组和哈希。

标量

标量表示单个值

my $animal = "camel";
my $answer = 42;

标量值可以是字符串、整数或浮点数,Perl 会根据需要自动在它们之间进行转换。您必须在第一次使用它们时使用 `my` 关键字声明它们。(这是 `use strict;` 的要求之一。)

标量值可以在各种情况下使用

print $animal;
print "The animal is $animal\n";
print "The square of $answer is ", $answer * $answer, "\n";

Perl 定义了许多具有简短名称的特殊标量,通常是单个标点符号或数字。这些变量用于各种目的,并在 perlvar 中有详细说明。您现在只需要了解 `$_`,它是“默认变量”。它用作 Perl 中许多函数的默认参数,并且由某些循环结构隐式设置。

print;          # prints contents of $_ by default
数组

数组表示一个值列表

my @animals = ("camel", "llama", "owl");
my @numbers = (23, 42, 69);
my @mixed   = ("camel", 42, 1.23);

数组从零开始索引。以下是如何访问数组中的元素

print $animals[0];              # prints "camel"
print $animals[1];              # prints "llama"

特殊变量 `$#array` 会告诉您数组中最后一个元素的索引

print $mixed[$#mixed];       # last element, prints 1.23

您可能很想使用 `$#array + 1` 来告诉您数组中有多少个元素。别费心了。实际上,在 Perl 期望找到标量值(“在标量上下文中”)的地方使用 `@array` 会为您提供数组中的元素数量

if (@animals < 5) { ... }

我们从数组中获取的元素以 `$` 开头,因为我们只从数组中获取单个值;您要求一个标量,您就会得到一个标量。

要从数组中获取多个值

@animals[0,1];                 # gives ("camel", "llama");
@animals[0..2];                # gives ("camel", "llama", "owl");
@animals[1..$#animals];        # gives all except the first element

这被称为“数组切片”。

您可以对列表执行各种有用的操作

my @sorted    = sort @animals;
my @backwards = reverse @numbers;

还有一些特殊的数组,例如 `@ARGV`(脚本的命令行参数)和 `@_`(传递给子例程的参数)。这些在 perlvar 中有详细说明。

哈希

哈希表示一组键值对

my %fruit_color = ("apple", "red", "banana", "yellow");

您可以使用空格和 `=>` 运算符来更清晰地排列它们

my %fruit_color = (
    apple  => "red",
    banana => "yellow",
);

要访问哈希元素

$fruit_color{"apple"};           # gives "red"

您可以使用 `keys()` 和 `values()` 获取键和值的列表。

my @fruits = keys %fruit_color;
my @colors = values %fruit_color;

哈希没有特定的内部顺序,尽管您可以对键进行排序并循环遍历它们。

就像特殊的标量和数组一样,也有一些特殊的哈希。其中最著名的是 `%ENV`,它包含环境变量。在 perlvar 中阅读有关它(以及其他特殊变量)的所有内容。

标量、数组和哈希在 perldata 中有更详细的说明。

可以使用引用构建更复杂的数据类型,引用允许您在列表和哈希中构建列表和哈希。

引用是一个标量值,可以引用任何其他 Perl 数据类型。因此,通过将引用存储为数组或哈希元素的值,您可以轻松地在列表和哈希中创建列表和哈希。以下示例展示了使用匿名哈希引用的 2 级哈希结构。

my $variables = {
    scalar  =>  {
                 description => "single item",
                 sigil => '$',
                },
    array   =>  {
                 description => "ordered list of items",
                 sigil => '@',
                },
    hash    =>  {
                 description => "key/value pairs",
                 sigil => '%',
                },
};

print "Scalars begin with a $variables->{'scalar'}->{'sigil'}\n";

关于引用主题的详尽信息可以在 perlreftutperllolperlrefperldsc 中找到。

变量作用域

在上一节中,所有示例都使用了以下语法

my $var = "value";

实际上,my 不是必需的;你可以直接使用

$var = "value";

但是,上述用法会在整个程序中创建全局变量,这是一种不好的编程习惯。my 创建的是词法作用域变量。这些变量的作用域限定在定义它们的代码块(即由花括号包围的一组语句)中。

my $x = "foo";
my $some_condition = 1;
if ($some_condition) {
    my $y = "bar";
    print $x;           # prints "foo"
    print $y;           # prints "bar"
}
print $x;               # prints "foo"
print $y;               # prints nothing; $y has fallen out of scope

在你的 Perl 脚本顶部使用 myuse strict; 结合,意味着解释器会捕获一些常见的编程错误。例如,在上面的示例中,最后的 print $y 会导致编译时错误,阻止你运行程序。强烈建议使用 strict

条件和循环结构

Perl 拥有大多数常见的条件和循环结构。

条件可以是任何 Perl 表达式。有关比较和布尔逻辑运算符的信息,请参阅下一节中的运算符列表,这些运算符通常用于条件语句。

if
if ( condition ) {
    ...
} elsif ( other condition ) {
    ...
} else {
    ...
}

它还有一个否定版本

unless ( condition ) {
    ...
}

这是 if (!condition) 的更易读版本。

请注意,即使代码块只有一行,花括号在 Perl 中也是必需的。但是,有一种巧妙的方法可以使你的单行条件代码块更像英语

# the traditional way
if ($zippy) {
    print "Yow!";
}

# the Perlish post-condition way
print "Yow!" if $zippy;
print "We have no bananas" unless $bananas;
while
while ( condition ) {
    ...
}

出于与 unless 相同的原因,它也有一个否定版本

until ( condition ) {
    ...
}

你也可以在后置条件中使用 while

print "LA LA LA\n" while 1;          # loops forever
for

与 C 完全相同

for ($i = 0; $i <= $max; $i++) {
    ...
}

由于 Perl 提供了更友好的列表扫描 foreach 循环,因此在 Perl 中很少需要 C 风格的 for 循环。

foreach
foreach (@array) {
    print "This element is $_\n";
}

print $list[$_] foreach 0 .. $max;

# you don't have to use the default $_ either...
foreach my $key (keys %hash) {
    print "The value of $key is $hash{$key}\n";
}

foreach 关键字实际上是 for 关键字的同义词。请参阅 "Foreach Loops" in perlsyn

有关循环结构的更多详细信息(以及本概述中未提及的一些结构),请参阅 perlsyn

内置运算符和函数

Perl 带有大量内置函数。我们已经看到的一些函数包括 printsortreverse。它们在 perlfunc 的开头列出,你可以使用 perldoc -f functionname 轻松阅读任何给定函数的文档。

Perl 运算符在 perlop 中有完整的文档,但以下是一些最常见的运算符

算术
+   addition
-   subtraction
*   multiplication
/   division
数值比较
==  equality
!=  inequality
<   less than
>   greater than
<=  less than or equal
>=  greater than or equal
字符串比较
eq  equality
ne  inequality
lt  less than
gt  greater than
le  less than or equal
ge  greater than or equal

(为什么我们要分别进行数字和字符串比较?因为我们没有特殊的变量类型,Perl 需要知道是按数字排序(99 小于 100)还是按字母顺序排序(100 在 99 之前)。)

布尔逻辑
&&  and
||  or
!   not

(andornot 不仅仅是在上面的表格中作为运算符的描述。它们本身也作为运算符支持。它们比 C 风格的运算符更易读,但优先级不同于 && 及其朋友。查看 perlop 获取更多详细信息。)

其他
=   assignment
.   string concatenation
x   string multiplication (repeats strings)
..  range operator (creates a list of numbers or strings)

许多运算符可以与 = 结合使用,如下所示

$a += 1;        # same as $a = $a + 1
$a -= 1;        # same as $a = $a - 1
$a .= "\n";     # same as $a = $a . "\n";

文件和 I/O

您可以使用 open() 函数打开一个用于输入或输出的文件。它在 perlfuncperlopentut 中有详细的文档,但简而言之

open(my $in,  "<",  "input.txt")  or die "Can't open input.txt: $!";
open(my $out, ">",  "output.txt") or die "Can't open output.txt: $!";
open(my $log, ">>", "my.log")     or die "Can't open my.log: $!";

您可以使用 <> 运算符从打开的文件句柄中读取。在标量上下文中,它从文件句柄中读取一行,在列表上下文中,它读取整个文件,并将每一行分配给列表中的一个元素

my $line  = <$in>;
my @lines = <$in>;

一次读取整个文件称为“吸入”。它可能很有用,但它可能占用大量内存。大多数文本文件处理可以通过 Perl 的循环结构逐行完成。

<> 运算符最常在 while 循环中使用

while (<$in>) {     # assigns each line in turn to $_
    print "Just read in this line: $_";
}

我们已经了解了如何使用 print() 打印到标准输出。但是,print() 也可以接受一个可选的第一个参数,指定要打印到的文件句柄

print STDERR "This is your final warning.\n";
print $out $record;
print $log $logmessage;

完成文件句柄后,您应该 close() 它们(虽然说实话,如果您忘记了,Perl 会为您清理)

close $in or die "$in: $!";

正则表达式

Perl 的正则表达式支持既广泛又深入,是 perlrequickperlretut 等大量文档的主题。但是,简而言之

简单匹配
if (/foo/)       { ... }  # true if $_ contains "foo"
if ($a =~ /foo/) { ... }  # true if $a contains "foo"

// 匹配运算符在 perlop 中有说明。它默认操作 $_,或者可以使用 =~ 绑定运算符绑定到另一个变量(也在 perlop 中有说明)。

简单替换
s/foo/bar/;               # replaces foo with bar in $_
$a =~ s/foo/bar/;         # replaces foo with bar in $a
$a =~ s/foo/bar/g;        # replaces ALL INSTANCES of foo with bar
                          # in $a

s/// 替换运算符在 perlop 中有说明。

更复杂的正则表达式

您不必只匹配固定字符串。实际上,您可以使用更复杂的正则表达式匹配几乎任何您能想到的东西。这些在 perlre 中有详细说明,但在此期间,这里有一个快速备忘单

.                   a single character
\s                  a whitespace character (space, tab, newline,
                    ...)
\S                  non-whitespace character
\d                  a digit (0-9)
\D                  a non-digit
\w                  a word character (a-z, A-Z, 0-9, _)
\W                  a non-word character
[aeiou]             matches a single character in the given set
[^aeiou]            matches a single character outside the given
                    set
(foo|bar|baz)       matches any of the alternatives specified

^                   start of string
$                   end of string

量词可用于指定要匹配的先前内容的数量,其中“内容”表示单个字符、上面列出的元字符之一,或括号中的字符或元字符组。

*                   zero or more of the previous thing
+                   one or more of the previous thing
?                   zero or one of the previous thing
{3}                 matches exactly 3 of the previous thing
{3,6}               matches between 3 and 6 of the previous thing
{3,}                matches 3 or more of the previous thing

一些简短的示例

/^\d+/              string starts with one or more digits
/^$/                nothing in the string (start and end are
                    adjacent)
/(\d\s){3}/         three digits, each followed by a whitespace
                    character (eg "3 4 5 ")
/(a.)+/             matches a string in which every odd-numbered
                    letter is a (eg "abacadaf")

# This loop reads from STDIN, and prints non-blank lines:
while (<>) {
    next if /^$/;
    print;
}
括号用于捕获

除了分组之外,括号还有第二个作用。它们可用于捕获正则表达式匹配部分的结果以供日后使用。结果最终会出现在 $1$2 等中。

# a cheap and nasty way to break an email address up into parts

if ($email =~ /([^@]+)@(.+)/) {
    print "Username is $1\n";
    print "Hostname is $2\n";
}
其他正则表达式功能

Perl 正则表达式还支持反向引用、前瞻以及各种其他复杂细节。在 perlrequickperlretutperlre 中详细了解它们。

编写子例程

编写子例程很容易

sub logger {
   my $logmessage = shift;
   open my $logfile, ">>", "my.log" or die "Could not open my.log: $!";
   print $logfile $logmessage;
}

现在我们可以像使用任何其他内置函数一样使用子例程

logger("We have a logger subroutine!");

什么是 shift?好吧,子例程的参数对我们来说是一个名为 @_ 的特殊数组(有关更多信息,请参阅 perlvar)。shift 函数的默认参数恰好是 @_。因此,my $logmessage = shift; 会将第一个项目从参数列表中移出并将其分配给 $logmessage

我们也可以用其他方式操作 @_

my ($logmessage, $priority) = @_;       # common
my $logmessage = $_[0];                 # uncommon, and ugly

子例程也可以返回值

sub square {
    my $num = shift;
    my $result = $num * $num;
    return $result;
}

然后像这样使用它

$sq = square(8);

有关编写子程序的更多信息,请参见 perlsub

面向对象 Perl

面向对象 Perl 相对简单,它使用引用来实现,这些引用根据 Perl 的包概念来识别它们是什么类型的对象。但是,面向对象 Perl 大致超出了本文档的范围。阅读 perlootutperlobj

作为一名初学者 Perl 程序员,您最常见的面向对象 Perl 使用方式是在使用第三方模块,这些模块将在下面进行介绍。

使用 Perl 模块

Perl 模块提供了一系列功能,可以帮助您避免重复造轮子,并且可以从 CPAN(http://www.cpan.org/)下载。Perl 分发版本身包含许多流行的模块。

模块类别从文本操作到网络协议,再到数据库集成和图形。CPAN 上也提供了一个模块分类列表。

要了解如何安装从 CPAN 下载的模块,请阅读 perlmodinstall

要了解如何使用特定模块,请使用 perldoc Module::Name。通常您需要 use Module::Name,这将使您能够访问导出的函数或模块的面向对象接口。

perlfaq 包含与许多常见任务相关的问答,并且通常会提供有关使用良好 CPAN 模块的建议。

perlmod 描述了 Perl 模块的概况。 perlmodlib 列出了随您的 Perl 安装一起提供的模块。

如果您想编写 Perl 模块,perlnewmod 将为您提供良好的建议。

作者

Kirrily "Skud" Robert <[email protected]>