清新的Perl(十一)——CPAN

2020年了,Perl语言在网络上并没有多少拥趸者,甚至有些人拿着Python同Perl比较并带有诋毁之意。但是,这并不妨碍Perl语言散发它的魅力,也不妨碍我们继续使用它。

我们之所以至今还在使用perl,不仅仅是正则表达式的魅力,也不仅仅是Perl语言性能丝毫不逊于Python,最重要的是Perl语言有一个强大的第三方模块库。我们可以这里找到所有我们想要的模块,去构建强大功能的程序,为我们的工作服务。

讲CPAN,离不开CPAN程序以及MetaCPAN网站。

Linux系统不需多讲,本身自带Perl语言,往往也带有CPAN工具。而windows,主要以Strawberry Perl和ActivePerl为主。我做网络工程师的,还是在windows用的比较多,而比较两款windows Perl,我推荐用Strawberry,不仅仅因为它保持了Linux的原汁原味,还有ActivePerl的包管理工具在国内很慢,可以说是没法用的。

下面,我们需要根据国内网络情况,修改cpan设置,让他能够迅速下载我们想要的配置模块(虽然按照windows讲的,但是Linux同样适用)。

当我们安装好Perl语言后,可以在命令行内调用cpan命令,进入cpan工具:

Loading internal logger. Log::Log4perl recommended for better logging
Unable to get Terminal Size. The Win32 GetConsoleScreenBufferInfo call didn't work. The COLUMNS and LINES environment variables didn't work. at C:\Strawberry\perl\vendor\lib/Term/ReadLine/readline.pm line 410.

There seems to be running another CPAN process (pid 8604).  Contacting...
Other job not responding. Shall I overwrite the lockfile 'C:\STRAWB~1\cpan\.lock'? (Y/n) [y] y

cpan shell -- CPAN exploration and modules installation (v2.27)
Enter 'h' for help.

cpan>

首先,通过o conf命令查看cpan配置文件:

cpan> o conf
$CPAN::Config options from C:\Strawberry\perl\lib/CPAN/Config.pm:
    commit             [Commit changes to disk]
    defaults           [Reload defaults from disk]
    help               [Short help about 'o conf' usage]
    init               [Interactive setting of all options]

    allow_installing_module_downgrades undef
    allow_installing_outdated_dists undef
    applypatch         []
    auto_commit        [1]
    build_cache        [50]
    build_dir          [C:\STRAWB~1\cpan\build]
    build_dir_reuse    [0]
    build_requires_install_policy [yes]
    bzip2              [ ]
    cache_metadata     [1]
    check_sigs         [0]
    cleanup_after_install undef
    colorize_debug     undef
    colorize_output    [0]
    colorize_print     [bold green]
    colorize_warn      [bold red]
    commandnumber_in_prompt [0]
    commands_quote     undef
    connect_to_internet_ok [1]
    cpan_home          [C:\STRAWB~1\cpan]
    curl               []
    dontload_hash      undef
    dontload_list      undef
    ftp                [C:\Windows\system32\ftp.exe]
    ftp_passive        [1]
    ftp_proxy          []
    ftpstats_period    undef
    ftpstats_size      undef
    getcwd             [cwd]
    gpg                []
    gzip               [ ]
    halt_on_failure    [1]
    histfile           [C:\STRAWB~1\cpan\histfile]
    histsize           [1000]
    http_proxy         []
    inactivity_timeout [0]
    index_expire       [1]
    inhibit_startup_message [0]
    keep_source_where  [C:\STRAWB~1\cpan\sources]
    load_module_verbosity [none]
    lynx               []
    make               [C:\STRAWB~1\c\bin\gmake.exe]
    make_arg           []
    make_install_arg   [UNINST=1]
    make_install_make_command [C:\STRAWB~1\c\bin\gmake.exe]
    makepl_arg         []
    mbuild_arg         []
    mbuild_install_arg [--uninst 1]
    mbuild_install_build_command undef
    mbuildpl_arg       []
    ncftp              []
    ncftpget           []
    no_proxy           []
    pager              [C:\Windows\system32\more.COM]
    password           undef
    patch              [C:\STRAWB~1\c\bin\patch.exe]
    patches_dir        undef
    perl5lib_verbosity [none]
    plugin_list        undef
    prefer_external_tar [0]
    prefer_installer   [MB]
    prefs_dir          [C:\STRAWB~1\cpan\prefs]
    prerequisites_policy [follow]
    proxy_pass         undef
    proxy_user         undef
    randomize_urllist  undef
    recommends_policy  [1]
    scan_cache         [atstart]
    shell              [C:\Windows\system32\cmd.exe]
    show_unparsable_versions [0]
    show_upload_date   [1]
    show_zero_versions [0]
    suggests_policy    [0]
    tar                [ ]
    tar_verbosity      [none]
    term_is_latin      [1]
    term_ornaments     [1]
    test_report        [0]
    trust_test_report_history [0]
    unzip              []
    urllist
        0 [http://mirrors.163.com/cpan/]
    urllist_ping_external undef
    urllist_ping_verbose undef
    use_prompt_default [0]
    use_sqlite         [1]
    username           undef
    version_timeout    [15]
    wait_list          undef
    wget               []
    yaml_load_code     [0]
    yaml_module        [YAML::XS]


cpan>

这里,我们主要看配置文件倒数第6行,urllist一项,当然我们也可以仅仅敲o conf urllist来直接查看配置:

cpan> o conf urllist
    urllist
        0 [http://mirrors.163.com/cpan/]
Type 'o conf' to view all configuration items

上面的配置文件,是我已经修改好的。在国内实际上,你只要百度“镜像站”一词,任何镜像站都会提供CPAN的镜像库。我比较常用的有两个中国科学技术大学USTC镜像站,还有网易开源镜像站:

http://mirrors.ustc.edu.cn/CPAN/
http://mirrors.163.com/cpan/

使用 o conf urrlist unshift 网址 来将镜像站加入:

cpan> o conf urllist unshift http://mirrors.ustc.edu.cn/CPAN/
commit: wrote 'C:\Strawberry\perl\lib/CPAN/Config.pm'

cpan> o conf urllist unshift http://mirrors.163.com/cpan/
commit: wrote 'C:\Strawberry\perl\lib/CPAN/Config.pm'

cpan> o conf urllist
    urllist
        0 [http://mirrors.163.com/cpan/]
        1 [http://mirrors.ustc.edu.cn/CPAN/]
Type 'o conf' to view all configuration items

这样,我们就完成CPAN下载资源的修改。

使用install 模块名称 来安装我们想要的模块,例如安装Net::Telnet模块:

cpan> install Net::Telnet
Fetching with LWP:
http://mirrors.163.com/cpan/authors/01mailrc.txt.gz
Fetching with LWP:
http://mirrors.163.com/cpan/modules/02packages.details.txt.gz
Fetching with LWP:
http://mirrors.163.com/cpan/modules/03modlist.data.gz
Database was generated on Tue, 18 Feb 2020 07:30:08 GMT
Updating database file ... Done!
Net::Telnet is up to date (3.04).

剩下最后一个问题,我们如何找到想要的模块呢?通常来说,就是百度,CSDN博客之类的博文站通常会有各种材料,我们仅需要记住模块名字,使用cpan install命令安装即可。

而更加权威的方式,是访问metacpan搜索想要的模块,当然这种方式前提是英文过关,每一个模块都有详尽的英语说明文档。

清新的Perl(十)——操作文本文档

对于Perl语言来说,我们最常用的便是利用正则表达式处理文档,包括分析设备日志、存储文档记录。而这些工作离不开Perl文件句柄的支持。因此本节,我会首先讲下Perl的文件句柄操作,也就是open函数。

open函数,在Perl语言中算是相当难理解的了,因为open函数中有符号。而且,因为Perl语言的写法很随意,所以在不同的教材中,有不同的写法,这更加让初学者感到糊涂了。我推荐open函数这样写:

open 句柄,读或写,文件路径及名称 or die "can not open the file: $!\n";

这种写法是最清晰的一种,无论是你将来维护代码,上传到GitHub分享给他人,都是最容易理解的。

句柄:实际上句柄是可以随便写的,但是作为初学者应该自律,我建议直接用“FILE”大写的英文单词表示。无论何时何地,当你要打开文件句柄时,记得把句柄命名为FILE。

读或写:由于Perl在符号使用方面过于泛滥了,书本教材中会介绍open模式中的很多符号。我建议只记住最常用的两种即可,<,>。这两个可以看作是简写的箭头,当箭头指向句柄,表示从文件中读取数据,当箭头指向文件,表示向文件中写入数据。

文件路径及名称:这个不用多说。

or die …:只要记住这是抛出异常的固定写法即可。

下面介绍一下读写操作:

#读操作
open FILE,'<','my.txt' or die "can't open the file:$!\n";
while(my $line=<FILE>){
    print $line;
}
close FILE;

这里讲解的是最常用的写法。首先,无论是读还是写,一定要记住在对句柄的操作完成后调用close FILE关闭句柄。虽然Perl会自动垃圾回收,但是养成关闭FILE句柄的好习惯,会保证你不用再运行程序时频繁的找bug。

对于读数据来说,最基本方法便是读取每行数据,再使用正则表达式解析。所有的工作都包括在一个while循环中,$line=<FILE>是固定写法,<>表示的是从文件句柄中读取一行,有很多源代码中会省略掉<>中的FILE句柄。这不是一种好习惯,因这种写法,我也用过,虽然不会报错,但是经常会读取到一些错误的数据。为了减少不必要的麻烦,请使用<FILE>这种写法,切勿省略句柄。

然后是写操作

open FILE,'>','my.txt' or die "can't open the file:$!\n";
print FILE "Hello World";
close FILE;

写操作更加的简单,调用print即可,记住句柄和字符串之间是空格,不是逗号。实际上,当我们使用print “Hello World”在屏幕上显示时,print和字符串正中间的标准输出句柄被省略了。

清新的Perl(九)——面向对象

对于一向标榜编程自由的Perl语言来说,面向对象的编程方式是不可缺少的。关于面向对象编程,我个人认为,讲解最好的是《Java编程思想(第四版)》。我学习编程语言,最先啃的一本书,便是它。说句实在话,当年啃书的方法不对,磕磕绊绊好多年才将它啃下来。而且,即便是啃下来了,还是不理解。直到后来,学完perl语言,开始进行Web开发,才真正的感悟到Java编程思想的精妙之处,可谓受益匪浅。

在这里,我相信读者均是为了学习perl而来,不推荐去生啃Java编程思想(万一都跑去学java就坏了)。对于任何一门编程语言来说,最重要的事情便是解决问题,无论是过程编程、还是面向对象编程,仅仅使用编程思想而已,没有对立和区别。当你实际进行开发时,很快会发现,一些问题用过程编程很简单,而一些问题用面向对象更简单。尤其是Perl语言,你会发现更加明显。

那么,何时我们会用到Perl的面向对象呢?实际上,很少,大部分问题用方法就能解决。即便你不看Perl各种教程、书籍中的面向对象一章的内容,丝毫不会影响你使用Perl解决问题。换句话讲,当你根据“清新的Perl”前八篇的要点看完一本Perl语言书籍、或者教程后,完全可以很好的做开发工作了。

现在回归正题,Perl语言的面向对象编程能解决什么问题?

这个问题,首先需要明白,想要学习Perl语言,用Perl语言解决的问题都是什么人?2020年了,我想基本上是像我一样的运维工程师、网络管理员了。

干运维这一行,xml、conf等一类的配置文件是经常要打交道的。Perl的正则表达式对于这类文件,是优秀的利器。比方说,VPN、防火墙、上网行为等设备,常常可以导入、导出xml配置文件。我们可以用Perl写一个脚本,去操作xml配置文件,增加、删除配置。

操作XML文件,我们需要从MetaCPAN上找第三方模块。例如XML::LibXML,这是一个面向过程的Perl模块,当你查看文档会发现,这个模块很复杂,包含的子模块繁多。但是这个模块可以完整的解析一个无论多复杂、多庞大的XML文件。

现在我们可以使用XML::LibXML模块,定制一个适合于我们设备配置文件的程序,辅佐我们方便地进行设备配置任务。当我们写完后,会发现这个程序很复杂,很啰嗦,同样也很难移植给其他场景使用。

假设,我们已经写好了一个配置防火墙ACL的XML文件。在使用一段时间后,我们发现有三个工作场景:

一,要从XML里面删除一些IP地址,同时要增加一些IP地址;二,仅仅从XML里面增加一些IP地址;三,仅仅从XML里面删除一些IP地址。

当我们只学过过程编程,而没有接触面向对象编程时,我们会有这样的思路:分为两个方法,一个方法负责删除、一个方法负责增加。这两个方法里面会有大量重复的代码,例如初始化XML::LibXML模块的代码、打开和写入XML文件代码等等。而且,这些重复的代码意味着计算机重复的工作——比如,每个方法内都要打开、再关闭保存一次文件。明明我们操作的是同一个XML,可为什么要每次都要先打开文件再进行增加或删除操作呢?

我们能否把这个文件看成一个对象,无论做多少次增加、删除IP地址的操作,我们都只打开、关闭保存一次文件呢?可以,这就是面向对象的编程方式。

我们将这个XML文件作为一个对象,把删除、增加两个方法关联到这个对象上,保证我们无论调用多少次删除、增加操作,都仅仅是在同一个XML文件对象上进行的。

下面,我们就可以简单写一个Perl的类了。

package Hello;
use strict;
use warnings;

use utf8;

#构造器
sub new {
    my $class = shift;

    # load
    my $doc = "Perl";

    my $self = {};

    $self->{doc} = $doc;

    bless $self,$class;

    return $self;
}

sub hello {
    my $self = shift;
    my $doc = $self->{doc};
    print "Hello World,$doc\n"; #窗口显示“Hello World,Perl”
}

1;

如上所示,Perl的类,和模块写法基本是一样的。区别在new中,new方法的目的在于创建一个对象,new方法关键的语句包括了:

#这一步创建一个类
my $class = shift;
#这一步创建一个对象——哈希(散列),类似上面的代码,可以将需要所有方法操作的变量保
#存在$self里面,比如说一个文件变量、XML变量等等
my $self = {};
#将哈希和类关联起来
bless $class,$self;
#返回对象
return $self;

在这一步里面,我们最主要的操作是得到一个对象,也就是$self散列,以及通过bless把类和$self关联起来,是$self真正的成为一个类的对象实例。

如何将方法和对象关联起来呢?我们仅需要记住每一个方法传输的第一个参数是$self:

my $self = shift; #放置于方法的第一行

以上,便是Perl语言的面向对象编程的主要内容。

清新的Perl(八)——包和模块

任何一门编程语言,之所以能够流行于世上,被程序员们所喜爱。其原因在于代码的能够重复利用,不仅仅可以在语言中方便、快捷的引入自己以前写过的代码,积累的各种轮子,而且还能使用各位编程大神分享出来的工具库。

Perl语言同样也不例外。

在Perl中,命名空间成为包(Package)。包提供了基本构造模块,基于这些构造模块,可以构建更高层次的模块和类概念。包,通常只是一个文件,文件名要与包名称相同,文件名后缀为.pm(perl module简写,意为perl模块)。模块在Perl中是可重用的基本单位。在我们自己的Perl脚本中,可以用“use 模块名”导入一个可重用的模块。

在讨论Perl语言的包和模块之前,我想说一句,如果单纯使用perl完成一些运维工作,写一些脚本之类的。仅仅知道使用use去导入模块就行了。但是,如果想写一些可以重复使用的代码,就需要继续阅读文章下面的部分。

一、符号表和限定名

在讲Perl语言的包和模块时,很多书籍都会讲这两个概念。但是,对于初学者,以及那些长期用于解决实际问题(大部分程序员都是如此)的人来说,不造高深的轮子,这两个概念仅仅知道即可,不必深入阅读(这两个概念,放在和任意种编程语言比较都很懵逼,都不如Rust的生命周期好理解!Perl难学就在于这种自己造的名词太多了有关)。下面,我就简单提一下这两个名词怎么理解:

1.符号表:我们的包或者模块的.pm文件,内部一般包含什么东西呢?可想而知,主要是定义的、需要在其他.pl文件中使用的函数、变量,而这些函数、标量组成的列表,就是符号表。符号表,是perl语言编译器自己用于知道一个.pm文件包含哪些函数、变量用的。

2.限定名:这个概念,在后续使用模块时会偶尔用到。限定名这个概念,主要指一个模块中的变量或者函数的全名。下面,我通过一个例子解释一下:

例如说(举个例子,实际模块中没有)在DBI模块中,存在一个$example_var变量和example_sub()方法,当我们在程序中使用use DBI引入模块后,如果在你的程序中同样存在一个$example_var变量和一个example_sub()方法时,怎么区分二者呢?

这就是限定名的作用,我们可以在名称前面增加DBI::前缀来表示DBI模块中的变量和方法,这就写作DBI::$example_var和DBI::example_sub()。对于我们自己程序中的变量和函数,实际上,同样存在一个限定名main::,只是在默认情况下可以省略,这就成为了main::$example_var和main::example_sub()。

二、包和模块名称

拿一些会经常使用的模块名称来说吧,例如telnet模块Net::Telnet,SSH模块Net::SSH,或者Cisco交换机配套的Net::Telnet::Cisco。

对于这些名称而言,可以这样理解(仅仅是理解,不一定正确):用“::”分割的词语,最后一位是模块的名称,而前面的几位,相当于分类、类别。例如说Net::前缀,表示的是网络类的模块,而Net::Telnet::Cisco来说,Net::Telnet::意味从Net::Telnet的进一步开发出来的模块。而这种起名称的方式,仅仅是字面意思,或者说是为了更好理解,实际上换其他名称也可以。

当然了,这些::标志分隔的单词,还有另一种意思,代表的放置.pm文件的路径名称,而::最后一位是文件名,从右向左依次为上级目录的名称。

例如 Net::Telnet::Cisco,代表的是Net/Telnet/Cisco.pm文件。而Net::Telnet代表的是,Net目录下的Telnet.pm,SSH.pm同理。

当我们使用use Net::Telnet::Cisco 时,perl会自动的将::替换为路径分隔符,并在末尾增加后缀.pm,然后去查找目录并引入模块文件。

三、AUTOLOAD

这个东西一般是用来写给其他开发者使用的模块,也就是造轮子时用的。其用法类似于java的接口、抽象类。基本是用不到的,可以跳过去。

感兴趣的可以自己去翻阅资料,不赘述。

四、构造属于自己的模块

构造模块很简单,根据前面名称和对应的目录放置模块,然后在.pm文件中添加标准的三句话即可,如下所示:

package Example;
#标准三件套:
require Exporter;
our @ISA = qw(Exporter);
our @EXPORTER = qw( helloworld ); #此处放置要导出使用的函数或变量
#end

sub helloworld {
    print "Hello World!/n";
}

1

五、总结

包和模块,相关书籍中涉及的概念很多、很乱,初学者头一次看会被搞得晕头转向。我并不指望各位读者通过阅读《清新的PERL》学会怎么用Perl语言,仅仅想作为一个学习Perl语言的思路,理清Perl中那些独特的、不流行的概念。

清新的Perl(七)——引用

前一篇博文中我讲了Perl函数的四个要点,对于初学者来说最先搞懂这四个要点,就可以用perl语言写一些简单的通用方法了(还请大家根据上篇的四个要点重点学习和阅读书籍材料)。而若想更加熟练的使用Perl函数,以及后续使用CPAN上的各种库,“引用”这道坎是必须要迈过去的。

说到引用,各大教材往往会先说符号引用,再说硬引用。在这里要说下,都2019年了,符号引用是老古董了,Perl5已经到5.30版本了(大部分通行的中文书籍,写的时候才5.10或是5.14版本,05年-09年时期的)。不要再一头扎进去先看符号引用了。重点要搞懂的是硬引用。

硬引用,是一个标量,记住是一个标量,代表的是变量内存地址

引用(以后泛指硬引用),广泛使用在数组和散列作为参数传递给函数,或者函数返回值两个方面。

#其使用方法也相当的简单,下面是一个数组变量和散列变量:
my @array;
my %hash;
#引用的表示方法:
$array_address = \@array; #获取数组变量的地址
$hash_address = \%hash;   #获取散列变量的地址

#如果我们从地址中重新获取变量怎么办呢:
@$array_address #表示数组变量和@array等价
%$hash_address #表示散列变量和%hash等价

在这里,有一个小提示,在CPAN上阅读模块的文档资料时,经常会出现单词reference,该单词就是引用的英文表达,说ref(简写,通常出现在变量名称中)或者reference通常情况下就是讲的引用。

一、作为参数的引用

前一章节中我已经讲过了Perl语言中函数传递的原理,是使用了一个数组变量@_。@_这个变量数组,具有十分强大的整合能力——这个意思是说,如果你函数的参数,传入的是两个数组变量,会被整合为一个数组@_。如果传入的是几个标量变量和几个数组,同样会被整合为一个数组@_。

当你真正要在函数内部使用时候数组变量的时候,你会发现,你根本无法分清在@_里的元素,哪些是原来的标量,哪些是原来数组的元素。

为了解决这个问题,我们就需要使用引用。通过将一个数组的内存地址作为标量直接传递到函数中,以此保证数组的完整性。

二、函数返回值

通常,作为初学者,不会需要设计一个函数返回一个引用。但是,cpan中的很多库,会用这种方式返回数据,最常用的是数据库查询返回值。

清新的Perl(六)——子函数

到现在为止,我已经讲了对于一位perl语言初学者来说,三大数据结构——标量、数组、散列的入门要点。本篇,我将讲解如何编写可以重复使用的函数。

Perl语言的函数,通常也叫作子例程或者方法(在面向对象编程模式下)。我们可以把数据结构的一些常用的处理方法编写为函数,以供反复使用。而编写函数,入门要点包括:

  1. 如何写一个简单的函数和函数调用。
  2. 如何给函数传递参数。
  3. 函数的返回值。
  4. 常用的变量限定词my。

一、写一个函数和函数调用

首先应该说明的是,perl函数的格式:

#函数声明的关键字为sub
sub 函数名{
   语句;
   语句;
   ……
}

函数应该如何调用呢?有两种形式,一种是函数名后面加括号“()”,另一种是函数名前面加与“&”。下面举个例子:

sub hello_world{
   print "Hello World!\n";
}

#调用:
hello_world();
&hello_world;

二、函数的参数

作为函数,没有参数就是没有灵魂,有输入、有输出的函数才是我们开发程序的最佳帮手。

而perl语言的函数声明比较特殊,他是没有形参的。开头永远是“ sub 函数名{} ”这种形式。

那么,我该如何写有参函数呢?不要急,我们先来看看有参函数的调用:

#我们来改进一下前面的hello_world函数,让他拥有两个参数,第一个是姓名,第二个是网址。显示结果为“Hello World! I am 姓名, Welcome to access my website: 网址!”:
#首先创建标量:
$name = 'Harry Liu';
$url = 'www.lwc365.top';
#调用函数:
hello_world($name,$url);

上面的例子中,我们加入了两个参数name和url,那么下一步我们来理解perl是如何将参数传递入函数的:

当我们调用需要调用的函数带参数时,参数会根据先后顺序放在一个内置数组变量“@_”中的,比如说上面例子中@_数组内容为($name,$url)。在函数内部,我们使用shift方法从@_数组中取出参数值(在数组一篇中我讲过,shift可以从数组的最前面取值),通常情况@_可以省略,只保留shift关键字。那么,对于hello_world函数,就会有下面这样的实现:

sub hello_word{
   $sub_name = shift;
   $sub_url = shift;
   #上面两句便是声明参数了.
   print "Hello World! I am $sub_name, Welcome to access my website: $sub_url!\n";
}

三、函数返回值

和其他语言一样,在Perl语言中返回值关键字为return。

四、最常用的限定词my

my作为一种安全的作用域限定词,在Perl语言中广泛的使用。尤其是在用strict模式编写代码时,如果声明的变量没有使用限定词my,那么运行前检查会报错。

限定词my的意思在于将变量限定在最小的作用域范围内。如果我们不适用my限定作用域,声明的变量就是全局变量——整个文件内有效。

清新的Perl(五)——散列(哈希)

本篇,我将讲解perl中另一个重要的数据结构——散列。散列,通俗讲就是什么都能装得下的“键值对”数据结构。散列的键,是字符串;而值可以是标量、数组甚至散列。

散列与数组组合使用,成为散列的数组、数组的散列是perl语言开发的黄金钥匙。

数据结构是编程的关键基础,像python、perl、ruby这样的脚本语言,最大的编程优势就在于拥有强大、简便的模块库,无论是操作excel表格、通过ssh远程服务器、开发网站等等操作,只需要调用库中的函数,便可以轻松的得到返回值。然后,将返回值放入标量、数组、散列或者三者的组合结构中,利用这三种变量提供的方法、正则表达式还有for、if控制结构实现对提取到的数据的加工。最后,再借助模块库,或是显示在屏幕中、或是存在数据库中、又或者用web展示出来。

对与perl语言的散列来说,入门学习阶段重点有三处:

  1. 如何初始化一个散列,如何赋值一个散列。
  2. 如何用for循环以及keys方法遍历散列中的键值对。
  3. 如何用exists判断散列中的某一个键是否存在。

一、散列的初始化和赋值

对于perl的散列,首先要记住的是散列符号%。用百分号%后面跟字母、下划线以及数字来声明一个散列变量。

%a;
%b;
%perl_hash;

在散列初始化赋值中,虽然perl语言有很多的形式,但是为了规范化和便于其他人阅读理解,我推荐养成使用“=>”符号来区分键值对:

%site = ('google'=>'google.com',
         'runoob'=>'runoob.com', 
         'taobao'=>'taobao.com');
#对于键,一直是字符串,所以可以不用加引号;但是值如果是字符串需要加引号:
%site = ( google =>'google.com',
          runoob =>'runoob.com', 
          taobao =>'taobao.com');
#我们可以增加一个新浪网址:
$site{sina} = 'sina.com';
#我们可以在散列site中加入一个数组:
@a = (1,2,3,4,5,6);
$site{array} =  \@a;#意为散列site的键array赋值了一个数组。
#我们来给散列赋值一个散列:
%b = (perl => "so easy!",
      python => "so easy too!");
$site{hash} = \%b;
#当然对于散列的散列赋值,更加通常的办法是:
$site{hash}{perl}="so easy!";
$site{hash}{python}="so easy too!";

#我们来读取散列中的值
#打印标量:
print $site{google}."\n"; #显示google.com
#打印数组中的值
print $site{array}[3]."\n";
#打印散列中的散列:
print $site{hash}{python};

以上就是perl的散列基本用法。其中对于数组和散列赋值给散列,需要使用“引用”技术,我将在后面的博文中进行讲解。

二、for循环和keys方法

perl散列与循环搭配,最常用的方法就是遍历散列中每一个键对应的值。常用格式如下:

%hash = (perl => "so easy!",
         python => "so easy too!",
         ruby => "it like perl!");
for $key (keys %hash){
   print $key."\t";        #打印键
   print $hash{$key}."\n"; #打印值
};

注意for语句的使用格式即可。

三、exists方法判断散列中的键

exists方法常用的场景很多,比如说:

用perl语言编写web后台,当前端使用JavaScript语言post发送json数据给后台时,perl语言会把json格式转化为散列(两者是完全相同的,极好理解),我们往会根据json数据包含的对象(也就是散列中的键)进行判断,并为用户返回不一样的网页内容。

exists方法使用格式如下:

if exists $hash{key} {
   print "yes!\n";
} else {
   print "no!\n";
}

好了,我们这一篇博文就结束了,散列方面请大家结合手头的书籍重点学习初始化、赋值、keys和exists即可入门。

下一篇,我将讲解perl语言的子函数。

清新的Perl(四)——数组以及jion函数

在数组学习方面,包含了四个要点:

  1. 数组如何赋值,包括把数字、字符和标量赋值给数组。
  2. 数组赋值后如何添加或删除新的元素。
  3. 获取数组长度以及用for、foreach如何遍历数组。
  4. 用join函数将数组变为字符串。

一 为什么用数组这种存储结构?

我们为什么需要数组这种存储结构呢?实际上,很多编程语言会把数组称作列表。列表引申为表格,在生活、工作中,我们处处需要表格——家里的账本、工作中天天处理的excel表格都是。表格,使我们需要的信息整齐、清晰、方便的展示出来,易懂又容易计算、整理。

同样,计算机对列表也是情有独钟的。我们之所以会使用计算机,原因在于计算机可以批量处理大量的数据,方便省时。而这种计算魅力源于“数组”、“判断”以及“循环”。

数组是循环结构的基础。无论哪种编程语言,想要用循环,就离不开数组的使用。

二 数组赋值及增删操作

数组赋值,前面讲了,网络上也有各种教程,就不多讲了。下面主要讲讲数组赋值后的增删操作,主要涉及四个函数,push、pop、shift、unshift。

嗯……先说下这四个函数能干什么。push和unshift两个函数都是往数组中添加元素,push往末尾添加(假设数组末尾标号为n,那么新元素就是a[n]),unshift往开头添加(新元素就是a[0])。pop和shift两个函数都是从数组中取值,pop从末尾取值,shift从开头取值。

实际上Perl中数组涉及到的函数,远不止这四个,更高级点的函数还有map函数(许多高级实践中经常会使用,但作为初学者来说,map函数实际上是对数组循环结构的一种简化)。

根据数组的实际使用场景,最常用到的是push和pop两个函数。在循环结构中,我们向数组中添加数据,我们会经常用到push。看个例子:

@array = ("苹果","梨","香蕉");
$a = "菠萝";
push @array,$a; #数组结果为"苹果","梨","香蕉","菠萝"

pop,虽说是第二常用的方法,但是实际使用场景并不多。大部分场景下,我们仅仅是需要遍历数组中的值,而不需要把值删除。根据我的个人经验,pop主要用在查找复杂目录结构时简化程序结构,比方说列出电脑C盘内所有文件的名称:

%title插图%num
c盘目录结构示意图

就像上面的示意图一样,我们很难预估C盘到底有多少层文件夹,每个文件夹中又有几个文件夹需要打开进行查找。这种时候,我们需要用一个数组作为待查找文件夹的存放池(@temp)。当我们打开C盘根目录时,一方面把每个文件的名称用push方法存储到一个数组中;另一方面,把所有的文件夹用push方法放入temp数组。之后,用pop方法从temp数组中取出文件夹,重复上面的操作。不断的循环处理,当检查到temp数组为空(没有任何值)时,我们就可以结束循环,打印存放文件名称的数组,完成C盘所有文件夹的查找了。

#!/usr/bin/perl

#编写此例时,查找文件夹中子文件夹和文件,我使用了glob函数
#glob函数可以返回目录中所有子项的绝对路径
#注意glob的参数字符串末尾要加“/*”

use strict; #使用strict模式,可以规范书写格式
my $dir = 'C:/*'; #要遍历的目录
my @temp;
my @all_files;

find_file($dir); #函数,原型在末尾
while(1){ #循环结构,用于遍历所有的子文件夹
    my $file = pop @temp;
    last if not $file; #如果temp数组中没有元素,就结束循环
    find_file($file);
}

foreach (@all_files){ #打印所有的文件
    print $_."\n";
}

sub find_file{
   my $dir = shift; #参数项

   my @files = glob( $dir ); #glob函数返回值为文件夹和文件绝对路径组成的列表
 
   foreach my $file (@files ){ #遍历列表中的每一项
      my @files1 = glob($file.'/*'); #通过glob函数,如果是文件夹
                                     #那么将返回一个列表,如果是
                                     #文件将返回一个空值
      if (@files1) {
          foreach my $file1 (@files1){
              push @temp,$file1;
          }
      } else {
          push @all_files,$file; #如果列表为空值,证明是文件,放入文件存储列表中
      }
   }

}

三 数组长度和for、foreach循环遍历

文章开篇提到数组是循环的基础,现在我们该来讲讲循环。正如上面一个例子中,我用到了while和foreach两种循环结构。作为本系列文章的主旨——“清新”,我们力求将perl最常用的、最简单的知识呈现在初学者面前,使大家能够快速入门perl语言。在这里,我要划重点,循环结构最常用到的for(foreach)和while,只此两种。

1.老旧的、不常用的for结构和数组长度:

这里指的是类似C语言的for结构。先搬一个例子过来:

#!/usr/bin/perl
 
# 执行 for 循环
for( $a = 0; $a < 10; $a = $a + 1 ){
    print "a 的值为: $a\n";
}

如果我们需要用这种结构遍历一个数组呢?我们需要先知道数组的长度:

#在perl语言中,数组长度通常使用如下方法获取
@array=(1,2,3,4,5,6);
print $#array;
#在数组名字array前面加$#表示返回数组最后一个元素的下标
#那么数组长度就是该值加1:
$len = $#array+1

下面,我们就可以遍历数组了:

for( $a = 0; $a < $#array+1; $a = $a + 1 ){
    print "array数组中下标$a 的值为: $array[$a]\n";
}

2.常用的for(foreach)

实际应用中,上面这种for结构基本用不到。一般用这种,用久了甚至会忘记前面这种写法……

抄抄前一节中列子:

foreach $file1 (@files1){
   push @temp,$file1;
}
#foreach 标量 (被遍历的数组){
# 使用标量……
#} 

在这种写法中,foreach可以简写为for,这就是为什么叫做for常用结构。

3.while结构

while(1){ #循环结构,用于遍历所有的子文件夹
    my $file = pop @temp;
    last if not $file; #如果temp数组中没有元素,就结束循环
    find_file($file);
}

while循环结构,一般括号内为循环条件,即while(condition){……}——符合条件便执行循环,不符合就退出循环执行下面的代码。

在程序设计时,我们经常会不确定何时结束循环。此时常用的结构就是while(while(1))死循环+循环控制关键字(next和last)。正如此例中,我们用到了last关键字,意思是当temp数组中没有元素时结束循环。而next关键字的意思是结束本次循环,不在执行{}内next后面的语句,进入下一次循环。

四 join函数

正如我们需要将标量字符串用split函数分割为数组一样,我们也常常需要将数组连接为标量字符串。

#!/usr/bin/perl
 
# 定义字符串
$var_string = "www-runoob-com";
$var_names = "google,taobao,runoob,weibo";
 
# 字符串转为数组
@string = split('-', $var_string);
@names  = split(',', $var_names);
 
 
# 数组转为字符串
$string1 = join( '-', @string );
$string2 = join( ',', @names );
 
print "$string1\n";
print "$string2\n";

下一篇,我将讲解perl散列的常用要点。

清新的Perl(三)——标量与字符串

前一篇,我简单介绍了Perl语言的变量——标量、数组和散列。主要讲三种变量的写法。从此篇起的三篇,我将讲解三种变量赋值和使用的常用方法。

一 标量

说起标量,作为Perl语言中最基本的变量单位,可以说是什么都能赋值给它。鞥?什么都能赋值?当然,即便是数组和散列都能赋值给它(后面,我会讲解这个,也就是Perl语言学习的难点——引用)。

在真正的使用中,标量一般有如下用处:

  1. 赋值数字,进行数学计算。
  2. 赋值字符串,进行文本处理。

Perl语言的主要优势在于文本处理。凡是在工作中遇到的需要处理文本的工作,都可以用Perl来实现。至于数学计算,和C语言是相似的,各种教程一大把,也就不需要我多说什么了。接下来,我主要讲讲字符串。

二 字符串

字符串,在学习正则表达式之前能够做的事情不多。做字符串处理嘛,目的就是要从一堆字符串里面提取有用的信息。在没有学习正则表达式之前,字符串处理方法,大家重点关注学习下面两点即可:

  1. 如何将字符串与字符串,字符串与变量进行合并连接。
  2. split函数,如何用split函数将一个字符串分割为多个变量或者数组。

首先讲的是字符串连接:

#两个字符串之间的连接使用“.”符号的:
$a = "I love"."Perl";
print $a; #屏幕上将显示I love Perl.
#字符串和变量连接:
$b = $a."Because it is so easy";
print $b; #屏幕上将显示I love Perl Because it is so easy

注意:关于Perl语言字符串的双引号(””)与单引号(”)间的区别,请自行百度。

然后,讲下split函数:

split函数主要作用是,将一个字符串根据其中的某一个字符进行分割,例如字母、数字、逗号、冒号、小数点等等各种符号。例如:

$a = '苹果,菠萝,香蕉';
$b,$c,$d = split /,/, $a; #现在$b为苹果,$c为菠萝,$d为香蕉

下面一个例子:

$a = '苹果 菠萝 香蕉';
$b,$c,$d = split / /, $a; #现在$b为苹果,$c为菠萝,$d为香蕉

三 要点解释

关于标量的学习,主要了解标量如何赋值即可。

关于字符串的学习,字符串连接很简单,重点放在学习split上,请多加练习和理解。

下一篇,我将讲解数组。

清新的Perl(二)——数据结构

一 简述

在网络上很多推荐学习“小骆驼”《Perl语言入门》来入门Perl语言。我个人观点,路子不对,小骆驼用全书四份之三的章节在讲数据结构——标量、数组、哈希,讲的太详细了,详细到看了半天,我都没明白Perl到底怎么用。简直是绕晕了。

哈哈哈,大家说说都绕晕了,能不出来说Perl学习曲线陡吗?

我在这里,凭借个人经验,把那些不常用的东西统统扔掉(想看的,后面溜了可以自己去学学)。当然了,请放心,Perl是可以实现“半部论语治天下”般的简单。

二 数据结构成员

Perl只有三种数据结构标量、数组和哈希。标量用$符号做开头,如$a $b $c;数组用@符号开头,如@a @b @c;哈希用%符号开头,如%a %b $c。

三 标量

$a = “标量是Perl语言的基础,无论是数字、字母、单词语或汉字、一长串的语句或整篇文章,都可以统统塞进去。”;

就像这个样子用。常规还如:

$b = 1;
$c = 2.0;
$d = 'a';
$e = 'Hello Perl!';
$f = '清新的Perl';

因为Perl是弱类型的语言,不用声明标量的类型,像这种方式的使用变量的还有JavaScript、Python、Lua、Ruby等语言。

总之,凡是需要处理的数据,都可以放在标量中。

四 数组

数组是做什么的?数组是一系列的标量组成列表,而且标量可以是不同的类型。具体的使用方式如下:

@hits = (25, 30, 40);
@names = ("google", "runoob", "taobao");
#数组的调用方式如下:
$hits[1] #值为25
$names[2] #值为“taobao”

在这里需要注意的是@符号仅表示数组,而数组中的单个值还是标量所以用$符号作为前缀。

五 哈希(散列)

哈希如同Python中的字典,JavaScript中的Json对象,实际上为键值对的组合。

标准的创建和赋值方式,仅需要记住一种常用的即可:

%data = ('google'=>'google.com', 'runoob'=>'runoob.com', 'taobao'=>'taobao.com');
#使用方式
$data{google} #值是"google.com"

大家也注意到了,perl赋值语句右侧的要赋值的内容,无论是数组还是散列都是以“()”包裹的。在数组中位置是用“[]”,散列中键是用“{}”。这就是Perl的赋值方式。有一点需要说明,散列中的键只能是字符串,所以‘=>’左侧、{}中的值可以不用加引号表示。

因此只需要记住,标量、数组、散列是通过$ @ %三个字符区分的。在数组和散列中获取特定的值时,数组中的位置跟其他语言一样用“[]”括起来,散列中的键要用花括号“{}”括起来。

下一篇,我将讲解标量和字符串。