|
perlfaq8 - perl 常问问题集,第八篇
篇名perlfaq8 - 系统互动(原文版 Revision: 1.21, Date: 1997/04/24 22:44:19. 中文版 $Revision: 1.1 $, $Date: 1998/03/25 03:19:06 $)
概述这部份的 Perl 常见问题集涵盖关於与作业系统互动的问题。这包括了程序间通讯 [interprocess communication (IPC)]、使用者介面的控制(键盘、萤幕以及指标 装置),以及几乎所有和资料处理无关的事情。 请阅读特别针对你所使用的作业系统下的 perl 所写的常见问题集和文件(例如, perlvms 、perlplan9,...),以取得 perl 在个别差异方面更详尽的资料。
如何得知使用者正在哪个作业系统下执行我的 perl 程式?$^O 这个变数(若使用 English 模组就是 $OSTYPE)会指出你的 perl 解译器执 行档是替哪个作业系统、平台所建的。
为什麽 exec() 不会传值回来?因为这正是它所做的:它用另一个不同的程式来取代你当时所执行的。如果你的程 式需要继续跑下去(这可能正是你问此问题的原因吧?),改用
如何对 键盘/萤幕/滑鼠 做些花样?连接/控制 键盘、萤幕和指标装置(「滑鼠」)的方法因作业系统的不同而有不 同;不妨试试下列模组:
如何向使用者询问密码?(这个问题跟全球资讯网一点关系也没有。如果你要找的是跟 WWW 有关的,那就 看另一份常见问题集吧。) 【译注:中文版的 Perl CGI 程式设计常见问题集可以在下列网址中找到: http://www.cnnb.net/tianyige/tppmsgs/msgs1.htm#114
在 crypt
里面有个范例。首先,将你的终端机设为「无回应」[no echo] 模式,然後就用平常的方法将密码读入。你可以用老式的 你也可以在大部份系统上使用 CPAN 里的 Term::ReadKey 模组,这个模组较易使用而且理论上也较据可携性/移植性。
如何对序列埠做读写动作?这端看你在什麽作业系统上执行你的程式。以 Unix 来说,序列埠可以透过 /dev 目录下的档案来撷取; 而在其他系统上,设备的名称无疑地会不一样。以下是一些在设备互动时可能遭遇的共同问题:
如何逆解加密後的密码档案?花大把大把的钱去买破解专用的硬体,这会让你成为焦点话题。 说正经的,如果是碰到 Unix 密码档的话就不行 - Unix 密码系统用的是单向的加 密函数。像 Crack 之类的程式可以暴力地(并聪明地)试着猜出密码,但无法 (也不能)保证速战速决。 如果你耽心的是使用者选取不良的密码,你应该在使用者换密码时主动审核(例如说修改
如何启动一个背景执行的程序?你可以使用: system("cmd &") 或是用 fork,像 fork 里写的(在 perlipc 里有更进一步的 范例)。如果你在 Unix 类的系统上的话,请注意以下几件事情:
如何捕捉 控制字元/讯号?你并不能真的 ``捕捉'' 一个控制字元。而是控制字元产生一个讯号让你捕捉。关於讯号的资料可以在 Signals 以及 Camel 书第六章里找到。 要小心的是,大多 C 程式库无法重新进入 [re-entrant]。因此当你要尝试着在一 个处理器里做
除非你极为小心,否则在一个讯号处理器中,唯一安全可做的是:设定一个变数後离开。而在第一个情况下,你在设定变数的时候应确定
例如: $Interrupted = 0; # 确定它有个值 $SIG{INT} = sub { $Interrupted++; syswrite(STDERR, "哇\n", 5); } 然而,因为系统呼叫会自己重新启动,你将会发现如果你用的是「慢的」呼叫,像 < FH>、read()、connect() 或
如何更动 Unix 系统上隐式密码档 (shadow password) 的内容?如果你的 perl 安装正确的话,在 perlfunc
里描述的 getpw*() 函数应该就能够读取隐式密码档了(只有读取权)。要更动该档案内容,做一个新的密码档(这个档案的格式因系统而异,请看
passwd(5) )然後用
如何设定时间和日期?假设你有足够的权限,你应该可以用 然而,如果你只是要更动你的时区,只消设定一个环境变数即可: $ENV{TZ} = "MST7MDT"; # unix 下 $ENV{'SYS$TIMEZONE_DIFFERENTIAL'}="-5" # vms system "trn comp.lang.perl";
如何能够针对小於一秒的时间做 sleep() 或 alarm() 的动作呢?如果你要比
如何测量小於一秒的时间?一般来说,你可能做不到。 Time::HiRes 模组(CPAN 有)在某些系统上能达到此 功能。 总之,你可能做不到。但是如果你的 Perl 支援 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() 或 setjmp()/longjmp() 的动作?(例外处理)第五版的 Perl 增加了 END 区块,可以用来模拟
use sigtrap qw(die normal-signals); Perl 的例外处理机制就是它的 如果你只对例外处理的部分有兴趣,试试 exceptions.pl 程式库(包含在标准 perl里)。 如果你要的是
为何我的 sockets 程式在 System V (Solaris) 系统下不能用?「不支援本协定」这个错误讯息又是什麽意思?有些 Sys-V 根底的系统,特别像 Solaris 2.X,已重新将一些标准的 socket常数 定义过了。由於这些常数在各种架构下都是定值,所以在 perl程式码中常被人写 死在里面。处理此问题的适当方式 是用 ``use Socket'' 来取得正确的值。 须注意尽管 SunOS 和 Solaris 在二进位执行档上相容,这些值是相异的。自己去 想为什麽吧。
如何从 Perl 里呼叫系统中独特的 C 函数?通常是写个外部的模组来处理 - 参看「我要如何学到将 C 与 Perl 连结在一起? [h2xs,
xsubpp]」 这问题的答案。然而,如果此函数是个系统呼叫,而你的系统 有支援 切记先查查看你的 perl 版本中所附的模组以及 CPAN 里的模组,因为也许某人已 经写了个这样的模组。
在哪里可以找引入档来做 ioctl() 或 syscall()?以前这些档案会由标准
perl 发行中所附的 h2ph 工具来产生。这个程式将 C 标 头档案里的
1. 进入最高使用者帐户 2. cd /usr/include 3. h2ph *.h */*.h 如果你的系统支援动态载入,那麽为了可携性、而且合理的做法是使用 h2xs(也 是 perl的标准配备)。这个工具将 C 标头档案转换成 Perl 的衍伸档案 (extensions)。 h2xs 的入门要看 perlxstut 。 如果你的系统不支援动态载入,你可能仍应使用 h2xs。参看 perlxstut 和 MakeMaker (简单来说,就是用 make perl 、而非 make来重 建一份使用新的静态连结的 perl)。
为何 setuid perl 程式会抱怨关於系统核心的问题?有些作业系统的核心有臭虫使得 setuid 程式在先天上就不安全。Perl提供你一些方法(在 perlsec 里有写)可跳过这些系统的缺陷。
如何打开对某程式既输入又输出的管道 (pipe)?IPC::Open2 模组(perl
的标准配件)是个好用的方法,它在内部是藉着pipe()、
为何用 system() 却得不到一个指令的输出呢?你把
$exit_status = system("mail-users"); $output_string = `ls`;
如何补捉外部指令的 STDERR?有叁种基本方式执行外部指令: system $cmd; # 使用 system() $output = `$cmd`; # 使用 反向引号 (``) open (PIPE, "cmd |"); # 使用 open() 在 在上述方法中,你可以在呼叫前更改档案描述元 (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 在呼叫 注意,在反向引号里你 必须 使用 Bourne shell (sh(1)) 重导的语法而非
你也可以使用 IPC::Open3 模组(perl 标准配备),但注意它的参数顺序和 IPC::Open2不一样(参看 Open3 )。
为何当管道开启失败时 open() 不会传回错误讯息?其实会,只是或许并非以你期望的方式。在遵循标准 在使用
在输出值是空的情境里使用反向引号有何不对?严格说起来,没啥不对。但从程式写作严谨与否来说,这样无法写出较易维护的程式码,因为反向引号有一个(可能很巨大的)传回值,而你却忽略它。同时这也是缺乏效率的方法,因为你得把每行所有的输出读进来、留一块记忆体给它们,然後再把它们丢开。人们常常做下列这种事: `cp file file.bak`; 然後它们就会想:「嘿,乾脆以後都用反向引号来执行程式好了。」这是馊主意, 因为反向引号的目的在补捉程式的输出;system() 函数才是用来执行程式的。 再看看下列这一行: `cat /etc/termcap`; 你还没有指定输出,所以它会浪费记忆体(就那麽一下子)。另外你也忘了检查 print `cat /etc/termcap`; 但在大部份情况下,这本来可以、而且也应该写成 system("cat /etc/termcap") == 0 or die "cat program failed!"; 这样可快速地得到输出(一产生出来就会得到,不用等到最後),并且检查传回值。
如何不经过 shell 处理来呼叫反向引号?这需要些技巧。本来是写成 @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; } 一如
为何给了 EOF(Unix 上是 ^D,MS-DOS 上是 ^Z)後我的程式就不能从 STDIN 读取东西了呢?因为某些 stdio 的 set error 和 eof 旗标需要清除。你可以用 POSIX 模组里定 义的clearerr()。这是在技术上正确的解决之道。还有一些较不保险的方法:
如何把 shell 程式转成 perl?学习 Perl 然後重写。说真的,没有简单的转换方式。用 shell 做起来很笨的工 作可以用 Perl 很轻松的做到,而就是这些麻烦之处使得 shell->perl 转换程式 非常不可能写得出来。在重新撰写程式的过程里,你会认清自己真正要做的工作为 何,也希望能够跳脱 shell 的管线资料流机制 [pipeline datastream paradigm], 这东西虽对某些事情很方便,但也常造成低效率。
perl 能处理 telnet 或 ftp 这种双向互动吗?试试 Net::FTP、TCP::Client 和 NET::Telnet 模组(CPAN 有)。 http://www.cnnb.net/tianyige/tppmsgs/msgs1.htm#121也有助於模拟 telnet 协定,但是 Net::Telnet 可能较容易使用。 如果你所要做的只是假装 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;
如何在 Perl 里达到 Expect 的功能?很久很久以前,有个叫做 chat2.pl 的程式库(perl 标准配备之一),但一直没 真正完工。现在,你的最佳选择就是从 CPAN 来的 Comm.pl 程式库。
有没有可能将 perl 的指令列隐藏起来,以躲避像 "ps" 之类的程式?首先要注意的是,如果你的目的是为了安全(例如避免人们偷看到密码),那你应该重写你的程式,把重要的资讯从参数中剔除。光是隐藏起来不会让你的程式变得完全安全。 如要真的把看得见的指令列改掉,你可以设定 $0 = "orcus [accepting connections]";
我在 perl script 里 {更动目录,更改我的使用环境}。为何这些改变在程式执行完後就消失了呢?如何让我做的修改显露出来?
如何关闭一个程序的档案把手而不用等它完成呢?假设你的系统支援这种功能,那就只要送个适当的讯号给此程序(参看 kill)。通常是先送一个 TERM 讯号,等一下下,然後再送个 KILL 讯号去终结它。
如何 fork 出一个背景执行 (daemon) 程序?如果你所指的是离线的程序(未与 tty 连线者),那下列的程序据说在大部份的 Unix系统都能用。非 Unix 系统的使用者应该检查 Your_OS::Process 模组看看有 没有其他的解决方案。
如何使我的程式和 sh 及 csh 一起执行?参看 eg/nih 这 script(perl 原始码发行的一部分)。
如何得知我是否正在互动模式下执行?问得好。有的时候 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"; }
如何让一个缓慢的事件过时?如同 Signals
和 Camel 书第六章里所描述的,用
如何设定 CPU 使用限制?使用 CPAN 里的 BSD::Resource 模组。
在 Unix 系统上如何避免产生僵 程序 (zombies)?使用 Signals 里面叫 reaper 的程式码,在接到 SIGCHLD 时会呼 叫wait(),或是用 fork 里面写的双 fork 技巧。
如何使用一个 SQL 资料库?有几个连接 SQL 资料库非常好的界面。参看 http://www.cnnb.net/tianyige/tppmsgs/msgs1.htm#122 里的 DBD::* 模组。
如何让 system() 在收到 control-C 後就离开?做不到。你需要摹仿
如何开启一个档案但不阻挡其他程序的阅读?如果你有幸使用到支援非阻挡性读取的系统(大部份 Unix
般的系统都有支援), 你只需要用 Fcntl 模组里的 O_NDELAY 或 O_NONBLOCK 旗标,配合 use Fcntl; sysopen(FH, "/tmp/somefile", O_WRONLY|O_NDELAY|O_CREAT, 0644) or die "can't open /tmp/somefile: $!";
如何安装一个 CPAN 模组?最简单的方法就是让 CPAN 这个模组替你代劳。这个模组包含在 5.004及以後的版 本中。如要手动安装 CPAN 模组,或是任何按规矩发展的 CPAN模组,遵循以下步 骤:
在 MakeMaker里面有更多关於建构模组的细节,并参考「如何保有 一份自己的模组/程式库目录?」这个问题。
如何保有一份自己的 模组/程式库 目录?当你建构模组时,在产生 Makefiles 时使用 PREFIX 选项: 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 里面的。
如何从终端机一次抓进一个按键?如果用 POSIX 模组时又该怎麽做?#!/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() }
作者、译者与版权Copyright (c) 1997 Tom Christiansen and Nathan Torkington. All rights reserved. 有关使用、( 转 )发行事宜,详见 perlfaq译者:陈彦铭、萧百龄 中译版着作权所有:陈彦铭、萧百龄及两只老虎工作室。本中译版遵守并使用与原文版相同的使用条款发行。 |