
前言
虽然 Perl 看起来像是一种过时的脚本语言,但它今天仍然有很多相关用途。
如果你喜欢 UNIX/Linux/BSD,那么你肯定学过 Perl 并用它编程。我很确定你也不止一次地使用过 Perl,也许是好几次。该语言于 1987 年作为通用 UNIX 脚本语言创建,但从那时起经历了许多变化(甚至催生了另一种编程语言 Raku)。 与此同时最新版的Perl最显著的功能是支持Android、群晖科技的网络存储盒、Atari ST电脑。Android用户可以在Android上原生地编译Perl,或者通过目前已经支持的三种CPU架构:ARM、MIPS和X86来交叉编译。
您可能将它用于偶尔的系统管理任务,在您的工具链中,或用于增强一些需要更多气体的 shell 脚本。但是 Perl 不仅仅是脚本。
但从 Reddit 或 Stack Overflow 上关于 Perl 的大量讨论来看,你可能认为它已经死了。它远未消亡,并且与今天的软件工程息息相关。

来自https://insights.stackoverflow.com/trends?tags=perl%2Cbash%2Cnode.js
是什么让它特别?
Perl 是一种高级语言。它是弱类型的,具有同步流,并且是一种解释性语言。它具有垃圾收集和出色的内存管理功能。Perl 5 是开源的,可以免费参与。
Perl 的主要优势在于文本处理。无论是基于正则表达式的方法还是其他方法,Perl 都非常适合日志文件分析、文本操作、文件的就地编辑以及针对特定字段值搜索结构化文本文件。
Perl 对 UNIX 非常友好。Perl 充当 UNIX 工具的包装器,并完全集成到操作系统语义中。其他语言不会尝试这样做。因此,它擅长管道、文件 slurping、进程间通信和其他令人讨厌的任务。与 C 一样,它可以创建在后台运行的 UNIX 守护程序或服务器进程。我们可以很容易地调用 Perl 守护进程来避免花费数小时在 C 上工作并避免几个安全漏洞。
与node.js 的npm一样,Perl 在CPAN中有一个充满活力的开发社区,其中包含大量 Perl 模块存档。你可以找到一个模块来做任何你想做的事情。大多数模块都是用纯 Perl 编写的,而不使用 C,尽管一些性能密集型模块有一个使用 C 来提高性能的 XS 组件。
通过 CPAN,您可以使用数据库驱动程序 (DBD) 模块将许多数据库(SQLite、MySQL、Postgres 等)封装在 Perl 代码中。这些使用 Perl 自己的语义将 DB 操作导出到统一的可移植 Perl 代码中,从而隐藏了数据库的复杂性。
Perl 支持数组、散列和引用,您可以使用它们以非常强大的方式进行编码,而无需深入考虑数据结构或算法。大多数 CPAN 模块为您提供了功能风格和面向对象的风格。通过给您选择,您几乎可以按照自己的方式完成任务。
什么样的问题使 Perl 成为自然的?
如上所述,Perl 在文本处理方面做得很好。它可以根据复杂的正则表达式语句搜索数据字段的 CSV 文件。它可以快速解析日志文件。它可以快速编辑设置文件。Perl 也是各种格式转换、生成 PDF、HTML 或 XML 的天然工具。
在互联网的早期,Perl 是许多基本网络任务的基础:通用网关接口 (CGI)、电子邮件中的 MIME 解码,甚至在客户端和服务器之间打开 websocket。它在这里仍然很出色,并且可以在不运行整个服务器应用程序的情况下处理网络任务。
对于高级 UNIX 用户,Perl 允许您自动执行几乎任何您喜欢的操作。您可以创建守护程序(小型、持续运行的程序),它们会在满足条件时自动执行操作。您甚至可以创建构建管道和自动化单元测试。
更简单的编码方式
在当今以事件循环为中心的 JavaScript、node.js 和 TypeScript 异步世界中,Perl 提供了非常直接的代码流,而 Perl 代码提供了简单性和可控性。
代码同步流动的事实在调试和开始编写工作代码方面有很大的不同。Perl 支持线程很长时间了,但我从未使用过它们。
Perl 最好在单个任务中运行——就其本身而言,它不是一种性能出色的语言。如果你今天想要性能,你有 JavaScript 和 C,除了增加的复杂性和调试噩梦。
Perl 包含许多以独特方式处理数据的专用运算符。您可以使用菱形<>
运算符来吃掉任何流、文件、套接字、管道、命名管道等。
regex 运算符=~
意味着正则表达式可以很容易地包含在函数中。
Perl 强调以你想要的方式得到你想要的哲学。
让我们检查一些代码示例以获得一些观点。
假设我们必须创建一个字符串的 sha256 摘要。
这就是你在 node.js 中的做法:
This is how you do in node.js.
const {
createHash
} = require('node:crypto');
const hash = createHash('sha256');
data = 'Stack Overflow is cool';
hash.update(data);
console.log(hash.copy().digest('hex'));
在 Perl 中有两种方法。一种是函数式方法:
use Digest::SHA qw(sha256_hex);
$data = 'Stack Overflow is cool';
$hexdigest = sha256_hex($data);
print("Functional interface :: " . $hexdigest . "\n");
另一种是面向对象的方法:
$sha = Digest::SHA->new('sha256');
$sha->add($data); # feed data into stream
$hexdigest = $sha->hexdigest;
print("OO interface ::" . $hexdigest);
这是你在 Python 中的做法:
import hashlib
m = hashlib.sha256()
m.update(b"Stack Overflow is cool")
print(m.hexdigest())
垃圾收集
对于一种主要被认为只适合编写脚本的语言,它具有垃圾收集功能。这是一种称为引用计数的简单形式,其中 Perl 计算对变量的引用数,如果没有更多引用(或者如果程序离开创建变量的范围),则回收这些变量。没有 C 怪物不得不free()
和介意你所有的malloc()
电话。
也没有与 node.js 一样的堆栈溢出地狱,其中意外的关闭会导致递归和崩溃。
如果某些事情没有按计划进行,您始终可以使用die()
诊断工具或找出原因。Data::Dumper
Perl 可以通过-d
开关在调试模式下运行,但我几乎没有使用过。
现在让我们将 Perl 与其他一些流行的语言进行对比以了解上下文。
Perl 与其他语言的比较
现在让我们再次将 Perl 与其他语言进行比较。非阻塞套接字 I/O 或文件读取怎么样?如何处理大数据?二进制数据呢?
在所有这些部门中,Perl 都可以发挥自己的力量。但是,对于二进制数据,使用 C 或其他东西会更好。Perl 确实有ord
, pack
, 和朋友。但是对于 SMTP、HTTP 等基于文本的协议,Perl 套接字 I/O 非常好。特别是使用菱形运算符 ,<>
来使用来自任何文件描述符的数据。
这些东西让你想起了什么……没错,UNIX。Perl 是一个活生生的例子,说明了如何一直遵循 UNIX 哲学来完成工作。
人们谈论 node.js 流等等,但在我看来,与 UNIX 和 Perl 世界相比,它看起来像是一个笑话。
然而,Perl 是不安全 PHP 的一个简单替代方案。但出于某种原因,世界不想放弃 PHP。与 PHP 相比,Perl 需要更多的知识并且学习曲线陡峭,但 Perl 就是 Perl。您可以在任何地方使用它并完成工作。
Perl 与 Python
Python 有一个交互式外壳,您可以在其中轻松开发代码和学习。这太棒了,真的可以帮助语言学习者。Python 是一种优秀的学习者的编程语言。
然而,Perl 有一个-c
开关,可以只编译代码来检查基本的语法错误。
Perl 使用 strict 和-w
flags 使其更能抵抗意外的变量拼写错误和范围问题。Python 不提供。
Python 是一个彻头彻尾的面向对象范式。Perl 是一个混合体。Python 提供了几个函数式编程概念,例如 lambda、map 和 Friends,但它仍然植根于OOP。
Perl 在子例程和其他高级用法中使用传统引用和散列语义方面投入更多。Python 尝试使用对象来完成它,有点像 node.js 的做法。
Python 有 Jupyter notebook,它将 Python 的强大功能带到了浏览器中。Python 脚本通常比 Perl 短。Python 具有更经济的语法,通过链接对象可以很好地节省代码行数,但 Perl 在其他领域大放异彩。
有时这不是苹果对苹果的比较,因为每种编程语言都有自己的优点和特定用途。
Perl 与 node.js
Node.js 是完全面向对象的,但函数是一等变量,这意味着您可以以任何方式使用函数名称并以创造性的方式调用它,但这可能会使初学者感到困惑。它是完全异步的。
node.js 程序流程对于初学者来说可能很可怕。即使是经验丰富的程序员也会在代码流和确定函数何时返回方面遇到困难。它可能导致回调地狱,尽管 Promise 和 async/await 使事情变得更好——如果它们被使用的话。但是事件循环和单线程 node.js 流使它更难用于一次性任务。
Perl 令人愉悦且更直接。通常,如果您希望使用第三方库来解决特定问题,您可以使用 node.js 或 Perl 来完成。两种语言都存在用于插入大多数第三方库的开源模块。
大多数时候,node.js 依赖于 package.json 和本地安装。Perl 依赖于系统范围内安装的依赖项或库/模块。
Perl 与 ksh/bash
嗯,这是一件有趣的事情。Perl 可以成为 shell 脚本作业的竞争者,因为它是一种脚本语言,对吗?但是 Perl 安装是考虑是否在 Raspberry Pi 等资源受限的环境中使用它或使用一些 shell 的一个因素。
Perl 确实提供了许多 shell 脚本所缺乏的非常好的东西,但这确实不需要进一步讨论。这不是一个有意义的比较。例如,我们不比较 ksh 和 Python,而是倾向于在相同的上下文中讨论 Perl。这是因为它的根源。否则,这没有任何意义。
Perl 的一些缺点
虽然我是 Perl 的坚定支持者,但让我们保持平衡,看看为什么它没有在 AI 等某些领域取得进展。在当今以 AI 和 ML 为中心的世界中,Python 似乎已经占据了非常重要的地位。
谈到 node.js 的性能和它的事件循环单线程性能,Perl 不是竞争者。
在性能和现代趋势的竞赛中,Perl 显然显得有些过时。但正如我们在上面看到的那样,它确实有一席之地。
为什么它在 2022 年仍然有意义
Perl 不会消失。那不会发生的。
它仍在 CGI 脚本中使用。它用于多个系统管理任务。Perl 还活着并且在踢。
在与其他库和实用程序的绑定方面,Perl 与其他选择一样好。例如,如果您想与libcurl或libtls或一些第三方开源库交谈,那么我们通常可以选择我们喜欢的语言。在这里,Perl 得到开箱即用的支持,您可以轻松完成工作。
Perl 在它擅长的方面大放异彩。并且只要它解决好的问题没有被其他工具解决,Perl 就会继续存在和发展。
结论
Perl 的文档和教程一直非常出色——也许有时过于冗长——但显然它们对开发人员友好。
希望本文根据当前趋势、使用统计数据和开发人员基础为 Perl 提供一个令人信服且合理客观的案例。程序员通常会受到与业务需求或经理不同的因素的影响。在这两种情况下,Perl 都提供了便利、快速的开发时间以及丰富的社区支持和工具。
奖励:您可以使用的 Perl 代码
MIME 解码示例
这是我不久前写的一个 MIME 解码。它通过对 MIME 正文进行递归解码来显示电子邮件。
#!/usr/bin/perl
use JSON;
use CGI;
use DBI;
use Sys::Syslog;
use Broker;
use Encode qw/decode/;
use File::Basename;
use MIME::Parser;
use File::Copy;
use File::Glob;
use File::Path;
$q = CGI->new;
print $q->header('application/json');
$json = JSON->new;
@mailBody =();
@mailText =();
openlog("--Show one quamail... :");
my $db = DBI->connect("dbi:Pg:host=/tmp", "postgres", undef, {AutoCommit => 0});
if(!defined($db)) {
print("error","Could not connect to Postgres db");
}
sub dump_entity {
my ($entity, $name) = @_;
defined($name) or $name = "'anonymous'";
my $IO;
# Output the body:
my @parts = $entity->parts;
if (@parts) { # multipart...
my $i;
foreach $i (0 .. $#parts) { # dump each part...
dump_entity($parts[$i], ("$name, part ".(1+$i)));
}
}
else { # single part...
# Get MIME type, and display accordingly...
my $type = $entity->head->mime_type;
my $body = $entity->bodyhandle;
if ($type =~ /text\/html/) {
if ($IO = $body->open("r")) {
while (defined($_ = $IO->getline)) {
push @mailText, $_;
}
$IO->close;
}
else { # d'oh!
print "$0: couldn't find/open '$name': $!";
}
} elsif ($type =~ /text\/plain/) {
push @mailText, "<pre>";
if ($IO = $body->open("r")) {
while (defined($_ = $IO->getline)) {
push @mailText, $_;
}
push @mailText, "</pre>";
$IO->close;
}
else { # d'oh!
print "$0: couldn't find/open '$name': $!";
}
} else { # binary: just summarize it...
my $path = $body->path;
my $size = ($path ? (-s $path) : '???');
$f = basename($path);
push @mailBody, "<p>Attached <a href=\"/$path\">$f<a> Size:: $size bytes </p>";
}
}
1;
}
$id = $q->param('id');
$stmt = "select envip,mailfile,headers,subject,size,fromid,toid,date from quamail where id = $id";
@row = $db->selectrow_array($stmt);
($envip,$mailfile, $headers, $sub, $size, $from , $to, $date) = @row;
push @mailBody, "<strong>From</strong>: $from<br/>";
push @mailBody, "<strong>To</strong>: $to<br/>";
push @mailBody, "<strong>Date</strong>: $date<br/>";
push @mailBody, "<strong>Subject</strong>: $sub<br/>";
push @mailBody, "<br/>";
$mfile = "/quamail/$mailfile";
$parser = MIME::Parser->new;
$parser->output_under("/tmp");
$entity = $parser->parse_open($mfile);
push @mailBody, "<br/>";
&dump_entity($entity);
push @mailBody, "<br/>";
push @mailBody, @mailText;
$h = {'mailBody' => [@mailBody]};
print($json->pretty->encode($h));
@delfiles=</tmp/msg*>;
$ign = rmtree(@delfiles, {verbose => 0});
IP地理位置查询
这是在 Perl 中完成的 IP 地理定位查找。
#!/usr/bin/perl
use File::Basename;
use lib dirname (__FILE__);
use SpamCheetahDBQuery;
use IO::Socket;
use JSON;
use CGI;
use Sys::Syslog;
$q = CGI->new;
$| = 1;
openlog("GeoIP Countrywise");
print $q->header('application/json');
$ipsock = "/tmp/ipsock";
@ips = SpamCheetahDBQuery::dbQuery("geoip");
%cntryHash = (), %h = ();
for $ip (@ips) {
$cntryHash{$ip} += 1;
}
sub queryapi {
($ip) = @_;
my $ipjson = IO::Socket::UNIX->new(
Type => SOCK_STREAM,
Peer => $ipsock);
print $ipjson $ip . "\n";
my $info = decode_json(<$ipjson>);
$cntry = $info->{'countryCode'};
$cntryName = $info->{'country'};
$h{$ip} = "$cntry,$cntryName";
syslog("info", "Country code $cntry");
}
for $ip (keys %cntryHash) {
&queryapi($ip);
}
%outh = ();
%tmph = ();
for $ip (keys(%cntryHash)) {
($cntry, $name) = split /,/, $h{$ip};
$h{$cntry} += $cntryHash{$ip};
$outh{$cntry} = { 'value' => $h{$cntry} };
$tmph{$name} = "$cntry,$h{$cntry}";
}
@table = ();
for $name (keys(%tmph)) {
($code, $val) = split /,/, $tmph{$name};
push @table, {"country" => $name,
"code" => lc($code),
"mails" => $val};
}
my @table = sort { $b->{'mails'} <=> $a->{'mails'} } @table;
if($#table gt 9) {
@table = @table[0..9];
}
$json = JSON->new;
if ($q->param('table')) {
print($json->canonical->pretty->encode(\@table));
} else {
print($json->canonical->pretty->encode(\%outh));
}
在上面的例子中,你发现 Perl 使用散列的效果很好。通常需要几年的时间来了解 Perl 才能以惯用的方式和有效地进行编码。
Perl 守护进程示例
这是我也使用的守护程序代码。
#!/usr/bin/perl
use Tie::File;
use Sys::Syslog;
use JSON;
use Proc::Daemon;
use Proc::PID::File;
use IO::Socket::INET;
sub refresh_mtaip {
local $/;
# XXX read spamcheetah config and store vals
open F, "/etc/spamcheetah.json";
$conf = <F>;
close(F);
$json = JSON->new;
$dec = $json->decode($conf);
%schash = %$dec;
$/ = "\n";
$mtaip = $schash{'mtaip'};
}
openlog("Activate_mta");
sub resume_relay {
syslog("info", "MTA is up resuming relay");
tie @conf, "Tie::File", "/etc/pf.conf";
for (@conf) {
chomp();
next if(/^#/);
if(/rdr-to 127\.0\.0\.1 port 6300/) {
last;
}
if(/port smtp\s*$/) {
$_ =~ s/\s+$//;
$_ .= ' rdr-to 127.0.0.1 port 6300';
}
}
untie @conf;
system("/sbin/pfctl -f /etc/pf.conf");
system("/usr/bin/pkill -HUP smtprelay");
syslog("info", "Nothing to do EXIT");
exit(0);
}
sub pass_thro {
tie @conf, "Tie::File", "/etc/pf.conf";
for (@conf) {
chomp();
next if(/^#/);
if(/rdr-to 127\.0\.0\.1 port 6300/) {
$_ =~ s/$&//;
}
}
untie @conf;
system("/sbin/pfctl -f /etc/pf.conf");
syslog("info", "ACTIVATED pass thro' as MTA $mta is down");
}
sub check_mta {
my $sock = new IO::Socket::INET (
PeerAddr => $mtaip,
PeerPort => '25',
Proto => 'tcp',
Timeout => 15
);
if($sock) {
&resume_relay;
}
}
&pass_thro;
Proc::Daemon::Init();
die "Already running!" if Proc::PID::File->running();
for(;;) {
syslog("info", "Running in pass thro' mode");
syslog("info", "Sleeping 2 minutes");
sleep(120);
&refresh_mtaip;
$res = &check_mta;
}
从上面的例子可以看出,我在 Perl 上投入了大量资金。如果你可以编写守护进程和后台服务器进程,那绝对不是脚本或自动化。
那是严肃的事情。
Perl CGI
在服务器端处理的世界中,在 JavaScript 无处不在和 HTML5 出现之前(以及在浏览器本身的功能和特性变得如此繁重之前),没有人可以使用没有 Perl CGI 的网站进行数据库查询和后端工作。
但是 Perl 和 CGI 似乎都很少在 Web 环境中被讨论。现在 HTTP 服务器在 node.js 中运行,无论是在Express、Hapi还是Koa框架上。
对于大多数 Web 应用程序来说,Perl 继续在 CGI 中大放异彩。可以轻松执行执行重要数据库查询的 CGI 脚本并将其包装到遵循 CGI 规则的 Perl 脚本中。以下是一些使用各种操作系统级进程间机制(如 UNIX 域套接字)的示例脚本。
e JSON;
use CGI;
use DBI;
use IO::Socket;
use Sys::Syslog;
use JSON;
$q = CGI->new;
print $q->header('application/json');
openlog("Dashboard");
$json = JSON->new;
my $proxysock = "/tmp/proxysock";
my $db = DBI->connect("dbi:Pg:host=/tmp", "postgres", undef, {AutoCommit
=> 1});
if(!defined($db)) {
print("error","Could not connect to Postgres db");
}
sub getstats {
my $proxy = IO::Socket::UNIX->new(
Type => SOCK_STREAM,
Peer => $proxysock);
print $proxy "DUMPSTATS";
@out = <$proxy>;
for(@out) {
chomp;
($parm,$n) = split/=/;
if ($parm eq "totalmailattemptcnt") {
$outh{"mails"}= $n;
} elsif ($parm eq "goodmailcnt") {
$outh{"goodmail"} = $n;
} elsif ($parm eq "numattcnt") {
$outh{"numatt"} =$n;
} elsif ($parm eq "regexcnt") {
$outh{"regmatch"} =$n;
} elsif ($parm eq "viruscnt") {
$outh{"viruses"} = $n;
} elsif ($parm eq "spamcnt") {
$outh{"spamcnt"} = $n;
} elsif ($parm eq "relaydeniedcnt") {
$outh{"relaydenied"} = $n;
} elsif ($parm eq "bannedrecipcnt") {
$outh{"badrecip"} =$n;
} elsif ($parm eq "bannedsendercnt") {
$outh{"badsender"} = $n;
} elsif ($parm eq "blockedmimecnt") {
$outh{"badatt"} = $n;
} elsif ($parm eq "rfcrejcnt") {
$outh{"rfcrej"} = $n;
} elsif ($parm eq "fqdnrejcnt") {
$outh{"fqdnrej"} = $n;
} elsif ($parm eq "dkimrejcnt") {
$outh{"dkimrej"} = $n;
} elsif ($parm eq "spfrejcnt") {
$outh{"spfrej"} = $n;
} elsif ($parm eq "helorejcnt") {
$outh{"helorej"} = $n;
} elsif ($parm eq "mailszcnt") {
$outh{"mailszrej"} = $n;
} elsif ($parm eq "rblrejcnt") {
$outh{"rblrej"} = $n;
} elsif ($parm eq "score") {
$outh{"score"} = $n;
} elsif ($parm eq "sender_nomx") {
$outh{"sender_nomx"} = $n;
} elsif ($parm eq "norevdns") {
$outh{"norevdns"} = $n;
} elsif ($parm eq "malware_attach") {
$outh{"malware_attach"} = $n;
} elsif ($parm eq "malware_url") {
$outh{"malware_url"} = $n;
}
}
}
sub query_mails {
$stmt = $db->prepare("select count(*) from mails;");
($mail_count) = $db->selectrow_array($stmt);
}
sub query_quarantine {
$stmt = $db->prepare("select count(*) from quamail;");
($qua_count) = $db->selectrow_array($stmt);
}
# XXX execution start
%outh = ();
&getstats;
&query_mails;
&query_quarantine;
$outh{"Mails"} = $mail_count;
$outh{"Quarantined"} = $qua_count;
syslog("info", $json->canonical->pretty->encode(\%outh));
print($json->canonical->pretty->encode(\%outh));
$db->disconnect;
如果没有服务器端处理,什么都不会移动。
作者:Girish Venkatachalam
链接:https://stackoverflow.blog/2022/07/06/why-perl-is-still-relevant-in-2022/
网友评论