用Perl烹饪,第二部分
编辑注:Perl烹饪手册的第二版已经发布,所以本周我们继续突出展示新版本中的食谱——供您品尝。本周的摘录包括第14章(“数据库访问”)和第18章(“互联网服务”)中的食谱。并且请务必下周再回来查看更多新的食谱,包括提取表格数据、对元素或文本进行简单更改以及使用HTML::Mason进行模板化。
示例食谱:不使用数据库服务器使用SQL
问题
您想进行复杂的SQL查询,但不想维护关系型数据库服务器。
解决方案
使用CPAN的DBD::SQLite模块
use DBI;
$dbh = DBI->connect("dbi:SQLite:dbname=/Users/gnat/salaries.sqlt", "", "",
{ RaiseError => 1, AutoCommit => 1 });
$dbh->do("UPDATE salaries SET salary = 2 * salary WHERE name = 'Nat'");
$sth = $dbh->prepare("SELECT id,deductions FROM salaries WHERE name = 'Nat'");
# ...
讨论
SQLite数据库存储在一个单个文件中,通过DBI构造函数中的dbname
参数指定。与大多数关系型数据库不同,这里没有数据库服务器——DBD::SQLite直接与文件交互。多个进程可以同时从同一数据库文件中读取(通过SELECT),但只有一个进程可以做出更改(而其他进程在更改进行时将被阻止读取)。
SQLite支持事务。也就是说,您可以对不同的表进行多个更改,但直到您提交它们,更新才不会写入文件。
use DBI;
$dbh = DBI->connect("dbi:SQLite:dbname=/Users/gnat/salaries.sqlt", "", "",
{ RaiseError => 1, AutoCommit => 0 });
eval {
$dbh->do("INSERT INTO people VALUES (29, 'Nat', 1973)");
$dbh->do("INSERT INTO people VALUES (30, 'William', 1999)");
$dbh->do("INSERT INTO father_of VALUES (29, 30)");
$dbh->commit( );
};
if ($@) {
eval { $dbh->rollback( ) };
die "Couldn't roll back transaction" if $@;
}
SQLite是一个无类型数据库系统。无论您在创建表时指定了什么类型,您都可以将任何类型(字符串、数字、日期、二进制大对象)放入任何字段。实际上,您甚至可以创建一个没有指定任何类型的表。
CREATE TABLE people (id, name, birth_year);
数据类型仅在比较发生时才会发挥作用,无论是通过WHERE子句还是当数据库需要排序值时。数据库忽略列的类型,只查看被比较的具体值的类型。与Perl一样,SQLite只识别字符串和数字。两个数字作为浮点值进行比较,两个字符串作为字符串进行比较,当比较两种不同类型的值时,数字总是小于字符串。
只有一种情况下SQLite会查看您为列声明的类型。要获取自动增长的列,例如唯一的标识符,请指定类型为“INTEGER PRIMARY KEY”的字段。
CREATE TABLE people (id INTEGER PRIMARY KEY, name, birth_year);
示例14-6展示了如何实现这一点。
示例14-6: ipk
#!/usr/bin/perl -w
# ipk - demonstrate integer primary keys
use DBI;
use strict;
my $dbh = DBI->connect("dbi:SQLite:ipk.dat", "", "",
{RaiseError => 1, AutoCommit => 1});
# quietly drop the table if it already existed
eval {
local $dbh->{PrintError} = 0;
$dbh->do("DROP TABLE names");
};
# (re)create it
$dbh->do("CREATE TABLE names (id INTEGER PRIMARY KEY, name)");
# insert values
foreach my $person (qw(Nat Tom Guido Larry Damian Jon)) {
$dbh->do("INSERT INTO names VALUES (NULL, '$person')");
}
# remove a middle value
$dbh->do("DELETE FROM names WHERE name='Guido'");
# add a new value
$dbh->do("INSERT INTO names VALUES (NULL, 'Dan')");
# display contents of the table
my $all = $dbh->selectall_arrayref("SELECT id,name FROM names");
foreach my $row (@$all) {
my ($id, $word) = @$row;
print "$word has id $id\n";
}
SQLite可以存储8位文本数据,但不能存储ASCII NUL字符(\0
)。唯一的解决方案是在存储和检索数据之前进行自己的编码(例如URL编码或Base64)。这对于声明为BLOB的列也适用。
另请参阅
“使用DBI执行SQL命令;”CPAN模块DBD::SQLite的文档;SQLite的主页http://www.hwaci.com/sw/sqlite/
示例食谱:在邮件中发送附件
问题
您想发送包含附件的邮件;例如,您想发送PDF文档。
解决方案
使用CPAN的MIME::Lite模块。首先,创建一个代表多部分消息的MIME::Lite对象
use MIME::Lite;
$msg = MIME::Lite->new(From => '[email protected]',
To => '[email protected]',
Subject => 'My photo for the brochure',
Type => 'multipart/mixed');
然后,通过attach
方法添加内容
$msg->attach(Type => 'image/jpeg',
Path => '/Users/gnat/Photoshopped/nat.jpg',
Filename => 'gnat-face.jpg');
$msg->attach(Type => 'TEXT',
Data => 'I hope you can use this!');
最后,发送消息,可选地指定如何发送它
$msg->send( ); # default is to use sendmail(1)
# alternatively
$msg->send('smtp', 'mailserver.example.com');
讨论
MIME::Lite模块创建并发送带有MIME编码附件的邮件。MIME代表多媒体互联网邮件扩展,是附加文件和文档的标准方式。然而,它不能从邮件消息中提取附件——为此您需要查看“从邮件中提取附件”的食谱。
在创建和添加到MIME::Lite对象时,将参数作为命名参数对的列表传递。该对传递了邮件头(例如,From
、To
、Subject
)以及特定于MIME::Lite的参数。一般来说,邮件头应带有尾随冒号。
$msg = MIME::Lite->new('X-Song-Playing:' => 'Natchez Trace');
然而,MIME::Lite接受表18-2中的标题,而不带尾随冒号。*
表示通配符,因此Content-*
包括Content-Type
和Content-ID
,但不包括Dis-Content
。
已批准 |
加密 |
接收 |
发送者 |
密送 |
来自 |
引用 |
主题 |
抄送 |
关键词 |
回复到 |
到 |
注释 |
消息-ID |
重发-* |
X-* |
内容-* |
MIME版本 |
退信路径 |
|
日期 |
组织 |
MIME::Lite选项的完整列表见表18-3。
数据 |
FH |
立即读取 |
日期戳 |
文件名 |
顶部 |
处置 |
标识 |
类型 |
编码 |
长度 |
|
文件名 |
路径 |
MIME::Lite选项及其值决定了附件的内容和方式。
Path
包含要附加数据的文件。
Filename
消息读取者保存文件的默认文件名。默认情况下,这是从Path
选项(如果指定了Path
)中获取的文件名。
Data
要附加的数据。
Type
要附加数据的Content-Type
。
Disposition
要么是inline
要么是attachment
。前者表示读取者应将数据作为消息的一部分显示,而不是作为附件。后者表示读取者应显示一个解码并保存数据的选项。这最多是一个提示。
FH
从中读取附件数据的打开文件句柄。
有几个有用的内容类型:TEXT
表示text/plain
,这是默认值;BINARY
类似地是application/octet-stream
的简称;multipart/mixed
用于带有附件的消息;application/msword
用于Microsoft Word文件;application/vnd.ms-excel
用于Microsoft Excel文件;application/pdf
用于PDF文件;image/gif
、image/jpeg
和image/png
分别用于GIF、JPEG和PNG文件;audio/mpeg
用于MP3文件;video/mpeg
用于MPEG电影;video/quicktime
用于Quicktime(.mov)文件。
发送消息的两种方式是使用sendmail(1)或使用Net::SMTP。通过调用带有第一个参数为"smtp"
的send
来指示Net::SMTP。剩余参数是Net::SMTP构造函数的参数。
# timeout of 30 seconds
$msg->send("smtp", "mail.example.com", Timeout => 30);
如果你计划创建多个MIME::Lite对象,请注意,以类方法的形式调用send
会更改发送消息的默认方式。
MIME::Lite->send("smtp", "mail.example.com");
$msg = MIME::Lite->new(%opts);
# ...
$msg->send( ); # sends using SMTP
如果你打算处理多个消息,还应该查看ReadNow
参数。这指定了附件的数据应立即从文件或文件句柄中读取,而不是在发送、写入或转换为字符串时读取。
发送消息不是你可以用它做的唯一事情。你可以获取最终消息作为字符串。
$text = $msg->as_string;
print
方法将消息的字符串形式写入文件句柄。
$msg->print($SOME_FILEHANDLE);
示例18-3是一个程序,它将命令行上给出的文件名作为附件发送。
示例18-3: mail-attachment
#!/usr/bin/perl -w
# mail-attachment - send files as attachments
use MIME::Lite;
use Getopt::Std;
my $SMTP_SERVER = 'smtp.example.com'; # CHANGE ME
my $DEFAULT_SENDER = '[email protected]'; # CHANGE ME
my $DEFAULT_RECIPIENT = '[email protected]';# CHANGE ME
MIME::Lite->send('smtp', $SMTP_SERVER, Timeout=>60);
my (%o, $msg);
# process options
getopts('hf:t:s:', \%o);
$o{f} ||= $DEFAULT_SENDER;
$o{t} ||= $DEFAULT_RECIPIENT;
$o{s} ||= 'Your binary file, sir';
if ($o{h} or !@ARGV) {
die "usage:\n\t$0 [-h] [-f from] [-t to] [-s subject] file ...\n";
}
# construct and send email
$msg = new MIME::Lite(
From => $o{f},
To => $o{t},
Subject => $o{s},
Data => "Hi",
Type => "multipart/mixed",
);
while (@ARGV) {
$msg->attach('Type' => 'application/octet-stream',
'Encoding' => 'base64',
'Path' => shift @ARGV);
}
$msg->send( );
另请参阅
MIME::Lite的文档
奥莱利公司与联合出版社最近发布了(2003年8月)Perl Cookbooks,第2版。
标签
反馈
这篇文章有问题吗?请在GitHub上打开一个issue或pull request来帮助我们:GitHub