请阅读特别针对你所使用的作业系统下的 perl 所写的常见问题集和文件(例如, perlvms 、perlplan9,...),以取得 perl 在个别差异方面更详尽的资料。
system()
。
Term::Cap perl 标准内建模组 Term::ReadKey CPAN Term::ReadLine::Gnu CPAN Term::ReadLine::Perl CPAN Term::Screen CPAN
Term::Cap perl 标准内建模组 Curses CPAN Term::ANSIColor CPAN
Tk CPAN
【译注:中文版的 Perl
CGI 程式设计常见问题集可以在下列网址中找到:
http://www.math.ncu.edu.tw/~chenym/FAQ/Perl/perl-cgi-faq/
http://2tigers.net/perl/perl-cgi-faq-chi/ 】
在 crypt 里面有个范例。首先,将你的终端机设为「无回应」[no echo] 模式,然後就用平常的方法将密码读入。你可以用老式的 ioctl()
函数、
POSIX 终端机控制函数(参看
POSIX
,和 Camel 书第七章),或是呼叫
stty 程式,这些方法的可携性/移植性程度都不一样。
你也可以在大部份系统上使用 CPAN 里的 Term::ReadKey 模组,这个模组较易使 用而且理论上也较据可携性/移植性。
sysopen()
和 Fcntl 模组(标准 perl 的一部分)内 的
O_RDWR|O_NDELAY|O_NOCTTY
。在 sysopen 里有对此方法更
详尽的解说。
print DEV "atv1\012"; # 对某些装置来说是错误的 print DEV "atv1\015"; # 对某些装置来说是对的
尽管对普通的文字档案,一个 ``\n'' 便可解决断行的问题,但目前在不同作业系统 间(Unix、DOS/Win 和 Macintosh),对於断行记号仍无统一标准,而只有用 ``\015\012'' 来当成 每行的结尾,然後再视需要去掉输出中不想要的部份。这 个做法尤其常用於 socket输出/输入 与自动洗清 (autoflushing),也是接下来 要讨论的主题。
print()
的时候每个字元都要送到你指定的装置去,那你应自动清洗
你的档案把手,旧方法是:
use FileHandle; DEV->autoflush(1);
比较新的方法是:
use IO::Handle; DEV->autoflush(1);
你可以用 select()
和 $|
变数来控制自动清洗的动作(参考 $|
和select
):
$oldh = select(DEV); $| = 1; select($oldh);
你也可能看到不使用额外的暂存变数的写法,例如:
select((select(DEV), $| = 1)[0]);
如同前一个项目所说的,这方法对 Unix 和 Macintosh 间的 socket 输出/入 没 用。在这种情况下,你得把你的行末字元写死在程式码内。
read()
或 sysread()
动作,则你需要安排一个闹 铃把手或提供一个逾时设定(参看 alarm)。如果你是用非阻挡式的 开档,那麽就要配合非阻挡性的读取,也就是说得用到4 个参数的 select()
来确 定此装置的
输出/入 是否已准备好了(参考
select
)。
说正经的,如果是碰到 Unix 密码档的话就不行 - Unix 密码系统用的是单向的加 密函数。像 Crack 之类的程式可以暴力地(并聪明地)试着猜出密码,但无法 (也不能)保证速战速决。
如果你耽心的是使用者选取不良的密码,你应该在使用者换密码时主动审核(例如
说修改 passwd(1)
程式加入这个功能)。
system("cmd &")
或是用 fork,像 fork 里写的(在 perlipc 里有更进一步的 范例)。如果你在 Unix 类的系统上的话,请注意以下几件事情:
system("cmd&")
的话不会有这样的问题。
$SIG{CHLD} = sub { wait };
在 Signals 有范例程式教你怎麽做。用 system("prog &")
的
话不会有僵 程序的问题。
要小心的是,大多
C 程式库无法重新进入 [re-entrant]。因此当你要尝试着在一 个处理器里做 print()
动作,而这个处理器是由另一个stdio 的动作所叫出来的 话,你的内部结构可能会处於失调状态,而程式可能会丢出记忆核心 (dump core)。 有的时候你可以用 syswrite()
取代 print()
以避免这个状况。
除非你极为小心,否则在一个讯号处理器中,唯一安全可做的是:设定一个变数後
离开。而在第一个情况下,你在设定变数的时候应确定 malloc()
不会被叫出来 (譬如,设定一个已经有值的变数)。
例如:
$Interrupted = 0; # 确定它有个值 $SIG{INT} = sub { $Interrupted++; syswrite(STDERR, "哇\n", 5); }
然而,因为系统呼叫会自己重新启动,你将会发现如果你用的是「慢的」呼叫,像
<
FH>、read()、connect() 或 wait(),那麽将它们停下的唯一办法是使
用
「跳远」的方式跳出来;也就是产生一个例外讯号。参看在
Signals 里对阻挡性 flock()
的逾时处理器的说明,或骆驼书第六
章。
pwd_mkdb(8)(参考
pwd_mkdb(5))来安装新的密码档。
date(1)
程式来设定系统的时间与日期。 (但没有针对个别程序修改时间日期的方法)这机制在 Unix、MS-DOS、Windows
和
NT
下都能用;VMS 下则要用
set time
。
然而,如果你只是要更动你的时区,只消设定一个环境变数即可:
$ENV{TZ} = "MST7MDT"; # unix 下 $ENV{'SYS$TIMEZONE_DIFFERENTIAL'}="-5" # vms system "trn comp.lang.perl";
sleep()
所提供的最小单位一秒更精细的话,最简单的方法就是用
select 里面写的 select()
函数。如果你的系统有 itimers 并支
援syscall(),你可以试试下面这个老范例 http://www.perl.com/CPAN/doc/misc/ancient/tutorial/eg/itimers.pl
.
总之,你可能做不到。但是如果你的 Perl 支援 syscall()
函数并支援类似 gettimeofday(2)
的系统呼叫,你也许可以这麽做:
require 'sys/syscall.ph';
$TIMEVAL_T = "LL";
$done = $start = pack($TIMEVAL_T, ());
syscall( &SYS_gettimeofday, $start, 0)) != -1 or die "gettimeofday: $!";
########################## # 在这做你要做的事 # ##########################
syscall( &SYS_gettimeofday, $done, 0) != -1 or die "gettimeofday: $!";
@start = unpack($TIMEVAL_T, $start); @done = unpack($TIMEVAL_T, $done);
# fix microseconds for ($done[1], $start[1]) { $_ /= 1_000_000 }
$delta_time = sprintf "%.4f", ($done[0] + $done[1] ) - ($start[0] + $start[1] );
atexit()的效果。当程式或执行
绪(thread) 终了时就会去呼叫该包装的
END 区块(参考
perlmod
文件)。但 是如果当程式被没有抓到的讯号终结了,END 区块就不会被呼叫到,所以当你用
END
时应再加上
use sigtrap qw(die normal-signals);
Perl 的例外处理机制就是它的 eval()
运算子。你可以把 eval()
当做 setjmp 而die()当做 longjmp
来使用。更详细的说明请参考
Signals
和 Camel书第六章里关於讯号的那段,尤其是描述有关
flock()
的逾时处理器那段。
如果你只对例外处理的部分有兴趣,试试 exceptions.pl 程式库(包含在标准 perl里)。
如果你要的是 atexit()
语法(以及 rmexit()),试试
CPAN 里的 AtExit
模组。
须注意尽管 SunOS 和 Solaris 在二进位执行档上相容,这些值是相异的。自己去 想为什麽吧。
syscall(),那麽可以用
syscall 函数(说明在
perlfunc
里)。
切记先查查看你的 perl 版本中所附的模组以及 CPAN 里的模组,因为也许某人已 经写了个这样的模组。
cpp(1)指令转换成内含副程式定义的档案,像
&SYS_getitimer,你可 以把它当做函数的参数。这样做并不怎麽完美,但通常可达成任务。简单的像
errno.h
、syscall.h
和socket.h 这些档案都没问题,但像 ioctl.h
这种较难的档案总是需要人工编辑。以下是安装 *.ph
档案的步骤:
1. 进入最高使用者帐户 2. cd /usr/include 3. h2ph *.h */*.h
如果你的系统支援动态载入,那麽为了可携性、而且合理的做法是使用 h2xs(也 是 perl的标准配备)。这个工具将 C 标头档案转换成 Perl 的衍伸档案 (extensions)。 h2xs 的入门要看 perlxstut 。
如果你的系统不支援动态载入,你可能仍应使用 h2xs。参看 perlxstut 和 MakeMaker (简单来说,就是用 make perl 、而非 make来重 建一份使用新的静态连结的 perl)。
fork()
和 exec()
来完成此工作。不过切记要读它文件里关於锁死的警告 (
Open2
)。
system()
和反向引号 (``) 的用法搞混了。 system()
会执行一个指令然後 传回指令结束时的状况资讯(以一个 16 进位值表示:低位元是程序中止所收到的 讯号,高位元才是真正离开时的传回值)。反向引号 (``)
执行一个指令并且把它 所送出的东西送到
STDOUT。
$exit_status = system("mail-users"); $output_string = `ls`;
system $cmd; # 使用 system() $output = `$cmd`; # 使用 反向引号 (``) open (PIPE, "cmd |"); # 使用 open()
在 system()
下,STDOUT
和
STDERR
都会输出到和 script 本身的
STDOUT,
STDERR相同的出处,除非指令本身将它们导向它处。反向引号和 open()
则
只
读取指令的
STDOUT
部份。
在上述方法中,你可以在呼叫前更改档案描述元 (file descriptor) 名称:
open(STDOUT, ">logfile"); system("ls");
或者使用 Bourne shell 的档案描述元重导功能:
$output = `$cmd 2>some_file`; open (PIPE, "cmd 2>some_file |");
也可以用档案描述元重导功能将 STDERR 导向到 STDOUT:
$output = `$cmd 2>&1`; open (PIPE, "cmd 2>&1 |");
注意你 不能 光是将 STDERR 开成 STDOUT 的复制,而不呼叫 shell来做这个 重导的工作。这样是不行的:
open(STDERR, ">&STDOUT"); $alloutput = `cmd args`; # stderr 仍然会跑掉
失败的原因是,open() 让
STDERR 在呼叫 open()
时往
STDOUT的方向走。然後反
向引号让 STDOUT的内容跑到一个字串变数里,但是没有改变
STDERR 的去向(它
仍然往旧的
STDOUT那里跑)。
注意,在反向引号里你 必须 使用 Bourne shell (sh(1)) 重导的语法而非 csh(1)的!至於为何
Perl 的 system()、反向引号和开管道都用
Bourne shell语
法的原因,可在下址找到: http://www.perl.com/CPAN/doc/FMTEYEWTK/versus/csh.whynot
你也可以使用 IPC::Open3 模组(perl 标准配备),但注意它的参数顺序和 IPC::Open2不一样(参看 Open3 )。
fork()/exec()
机制的系统 上(例如,Unix),运作原理是这样的:open()
导致一个 fork()。在父程序里,
open()传回子程序的ID。然後子程序
exec()
从管道传来/出
的指令。父程序无 法得知 exec()
的动作成功与否-它能传回的只有 fork()
动作成功与否的消息。 要找出这指令是否顺利执行,你得补捉
SIGCHLD讯号并 wait()
以得到子程序离开 时的状态。如果你要写资料到子程序,则 SIGPIPE也该一并捕捉-否则在你写入之 前可能无法察觉 exec()
动作已失败了。这些在
perlipc
文件里都有说明。
在使用 spawn()
机制的系统里,open() 也许
能达到你所期望的-除非 perl 使用一个 shell 来起始你的指令。在这情况下以上对 fork()/exec()
的描述仍适
用。
`cp file file.bak`;
然後它们就会想:「嘿,乾脆以後都用反向引号来执行程式好了。」这是馊主意, 因为反向引号的目的在补捉程式的输出;system() 函数才是用来执行程式的。
再看看下列这一行:
`cat /etc/termcap`;
你还没有指定输出,所以它会浪费记忆体(就那麽一下子)。另外你也忘了检查
$?
看看程式是否正确的执行。即使你写成
print `cat /etc/termcap`;
但在大部份情况下,这本来可以、而且也应该写成
system("cat /etc/termcap") == 0 or die "cat program failed!";
这样可快速地得到输出(一产生出来就会得到,不用等到最後),并且检查传回值。
system()
同时具有直接决定是否先做 shell 万用字元 (wildcard)处理的功能,
反向引号就不行。
@ok = `grep @opts '$search_string' @filenames`;
你得改成:
my @ok = (); if (open(GREP, "-|")) { while (<GREP>) { chomp; push(@ok, $_); } close GREP; } else { exec 'grep', @opts, $search_string, @filenames; }
一如 system(),当你
exec()
一个序列时不会有 shell 解译的情况发生。
$where = tell(LOG); seek(LOG, $where, 0);
seek()
档案的另一部份然後再找回来。
seek()
档案另一个相异的的部份,读点东西,再回去找。
如果你所要做的只是假装 telnet 但又不要起始 telnet 时的沟通程序,那麽以下 这个标准的双程序方式就可以满足你的需要了:
use IO::Socket; # 5.004 才有的新模组 $handle = IO::Socket::INET->new('www.perl.com:80') || die "无法接上 www.perl.com 的 port 80: $!"; $handle->autoflush(1); if (fork()) { # XXX: undef 表示失败 select($handle); print while <STDIN>; # 将所有从 stdin 来的丢到 socket } else { print while <$handle>; # 将所有 socket 来的丢到 stdout } close $handle; exit;
如要真的把看得见的指令列改掉,你可以设定 $0
这个变数值,如同 perlvar
里写的。但这方法并非各种作业系统都适用。像 sendmail之类的背景程式 (daemons)
就将它们的状态放在那儿:
$0 = "orcus [accepting connections]";
eval()你
script 的输出来装出这种效果,在 comp.unix.questions
FAQ 里有详 细内容。
%ENV
的更改会持续到 Perl 离开,但是目录更动则不会。
fork && exit;
-t STDIN
和 -t STDOUT
可以提供线索,有时不行。
if (-t STDIN && -t STDOUT) { print "Now what? "; }
在 POSIX 系统中,你可以用以下方法测试你自己的程序群组与现在控制你终端机 的是否相同:
use POSIX qw/getpgrp tcgetpgrp/; open(TTY, "/dev/tty") or die $!; $tpgrp = tcgetpgrp(TTY); $pgrp = getpgrp(); if ($tpgrp == $pgrp) { print "前景\n"; } else { print "背景\n"; }
alarm()
函数, 或许再配合上一个讯号处理器。你也可以改用
CPAN 里更具弹性的 Sys::AlarmCall
模组来做。
system()
呼叫(参看 perlipc
里的范例程式),然後设计一个讯号处理器,让它把
INT 讯号传给子程序。
sysopen():
use Fcntl; sysopen(FH, "/tmp/somefile", O_WRONLY|O_NDELAY|O_CREAT, 0644) or die "can't open /tmp/somefile: $!";
如果你用的 perl 版本在编译时没有建入动态连结的功能,那你只消把第叁步 (make)换成 make perl 然後你就会得到一个新的 perl 执行档,里头连 有你新加入的延伸。
在 MakeMaker里面有更多关於建构模组的细节,并参考「如何保有 一份自己的 模组/程式库目录?」这个问题。
perl Makefile.PL PREFIX=/u/mydir/perl
然後在执行用到此 模组/程式库 的程式前先设好 PERL5LIB 环境变数(参考 perlrun ),或是用
use lib '/u/mydir/perl';
进一步的资料可在 Perl 的 lib 手册中找到。
use FindBin; use lib "$FindBin:Bin"; use your_own_modules;
PERLLIB 环境变数 PERL5LIB 环境变数 perl -Idir 指令列参数 use lib pragma, as in use lib "$ENV{HOME}/myown_perllib";
後者特别有用,因为它知道与机器相关的架构。lib.pm 机制模组是从 5.002 版开 始包含在 Perl 里面的。
#!/usr/bin/perl -w use strict; $| = 1; for (1..4) { my $got; print '给我: '; $got = getone(); print "--> $got\n"; } exit;
BEGIN { use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
$fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new(); $term->getattr($fd_stdin); $oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON; $noecho = $oterm & ~$echo;
sub cbreak { $term->setlflag($noecho); $term->setcc(VTIME, 1); $term->setattr($fd_stdin, TCSANOW); }
sub cooked { $term->setlflag($oterm); $term->setcc(VTIME, 0); $term->setattr($fd_stdin, TCSANOW); }
sub getone { my $key = ''; cbreak(); sysread(STDIN, $key, 1); cooked(); return $key; }
} END { cooked() }
译者:陈彦铭、萧百龄
中译版着作权所有:陈彦铭、萧百龄及两只老虎工作室。本中译版遵守并使用与原 文版相同的使用条款发行。