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 在个别差异方面更详尽的资料。
$^O 这个变数(若使用 English 模组就是 $OSTYPE)会指出你的
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
(这个问题跟全球资讯网一点关系也没有。如果你要找的是跟
WWW 有关的,那就 看另一份常见问题集吧。)
【译注:中文版的 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 模组,这个模组较易使
用而且理论上也较据可携性/移植性。
这端看你在什麽作业系统上执行你的程式。以
Unix 来说,序列埠可以透过 /dev 目录下的档案来撷取;
而在其他系统上,设备的名称无疑地会不一样。以下是一些
在设备互动时可能遭遇的共同问题:
- 锁档 (lockfiles)
- 你的系统可能会使用锁档来控制多重读写的情况。确定你用的是正确的协定。因为
当多个程序同时对一个装置做读取时可能会发生意想不到的情况。
- 开档模式
- 如果你打算对一个装置同时做读与写的动作,你得将它开到更新的模式(
在 open
里有更详细的解说)。如果你不希望冒着阻挡其他程序读取
这个装置的风险,那就得用
sysopen() 和 Fcntl 模组(标准 perl
的一部分)内 的 O_RDWR|O_NDELAY|O_NOCTTY。在 sysopen
里有对此方法更 详尽的解说。
- 档案尾
- 有些装置会等着在每行结尾处看到一个 ``\r'',而非
``\n''。在某些平台上的 perl, ``\r''和 ``\n'' 与它们平常(在 Unix
上)所指的 ASCII 值 ``\015'' 和 ``\012'' 有
所不同。你也许得直接给定数值,例如用八进位 (``\015'')、十六进位
(``0x0D''), 或指定控制字元 (``\cM'')。
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 输出/入 没
用。在这种情况下,你得把你的行末字元写死在程式码内。
- 不挡式输入 (non-blocking
input)
- 如果你正在做一个具阻挡性的
read()
或 sysread() 动作,则你需要安排一个闹
铃把手或提供一个逾时设定(参看 alarm)。如果你是用非阻挡式的
开档,那麽就要配合非阻挡性的读取,也就是说得用到4 个参数的 select()
来确 定此装置的 输出/入 是否已准备好了(参考 select
)。
花大把大把的钱去买破解专用的硬体,这会让你成为焦点话题。
说正经的,如果是碰到 Unix
密码档的话就不行 - Unix 密码系统用的是单向的加 密函数。像 Crack
之类的程式可以暴力地(并聪明地)试着猜出密码,但无法
(也不能)保证速战速决。
如果你耽心的是使用者选取不良的密码,你应该在使用者换密码时主动审核(例如
说修改 passwd(1) 程式加入这个功能)。
你可以使用:
system("cmd &")
或是用 fork,像 fork
里写的(在 perlipc
里有更进一步的 范例)。如果你在 Unix
类的系统上的话,请注意以下几件事情:
- STDIN, STDOUT 和 STDERR
是共享的
- 主程序和背景程序(即「子」程序)共用同一个
STDIN、STDOUT 和 STDERR 档案
把手。如果两个程序想同时去读、写同一个档案把手,就可能有怪事会发生。你也
许应该替子程序关闭或重新开启这些把手。你可以用开启一个管道
(pipe) 的方法 避免这些问题(参看 open)但是在某些系统上这样做会强迫子程序
必须比父程序早死。
- 讯号
- SIGCHLD、可能还有 SIGPIPE
这两个讯号要抓到。当背景程序执行完成後就会送出 SIGCHLD
讯号。而当你写入一个子程序已经关闭的档案把手时就会收到 SIGPIPE
讯号(一个未抓住的 SIGPIPE 可能导致你的程式无声无息地死去)。用
system("cmd&") 的话不会有这样的问题。
- 僵 程序
- 你得做准备,在子程序结束时「收成」它:
$SIG{CHLD} = sub { wait };
在 Signals
有范例程式教你怎麽做。用 system("prog &") 的
话不会有僵 程序的问题。
你并不能真的 ``捕捉''
一个控制字元。而是控制字元产生一个讯号让你捕捉。关於
讯号的资料可以在 Signals
以及 Camel 书第六章里找到。
要小心的是,大多 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() 的逾时处理器的说明,或骆驼书第六
章。
如果你的 perl 安装正确的话,在 perlfunc
里描述的 getpw*() 函数应该就
能够读取隐式密码档了(只有读取权)。要更动该档案内容,做一个新的密码档
(这个档案的格式因系统而异,请看 passwd(5) )然後用 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
.
一般来说,你可能做不到。 Time::HiRes 模组(CPAN
有)在某些系统上能达到此 功能。
总之,你可能做不到。但是如果你的 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] );
第五版的 Perl 增加了 END
区块,可以用来模拟 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 模组。
有些 Sys-V 根底的系统,特别像 Solaris 2.X,已重新将一些标准的
socket常数 定义过了。由於这些常数在各种架构下都是定值,所以在
perl程式码中常被人写 死在里面。处理此问题的适当方式 是用 ``use
Socket'' 来取得正确的值。
须注意尽管 SunOS 和 Solaris
在二进位执行档上相容,这些值是相异的。自己去 想为什麽吧。
通常是写个外部的模组来处理 -
参看「我要如何学到将 C 与 Perl 连结在一起? [h2xs, xsubpp]」
这问题的答案。然而,如果此函数是个系统呼叫,而你的系统
有支援 syscall(),那麽可以用 syscall 函数(说明在 perlfunc
里)。
切记先查查看你的 perl
版本中所附的模组以及 CPAN 里的模组,因为也许某人已
经写了个这样的模组。
以前这些档案会由标准 perl 发行中所附的
h2ph 工具来产生。这个程式将 C 标 头档案里的 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)。
有些作业系统的核心有臭虫使得 setuid
程式在先天上就不安全。Perl提供你一些 方法(在 perlsec
里有写)可跳过这些系统的缺陷。
IPC::Open2 模组(perl
的标准配件)是个好用的方法,它在内部是藉着pipe()、 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 解译的情况发生。
因为某些 stdio 的 set error 和 eof
旗标需要清除。你可以用 POSIX 模组里定 义的clearerr()。这是在技术上正确的解决之道。还有一些较不保险的方法:
- 试着保存搜寻指标然後去找它,例如:
$where = tell(LOG);
seek(LOG, $where, 0);
- 如果那样行不通,试着去
seek()
档案的另一部份然後再找回来。
- 如果还是行不通,试着
seek()
档案另一个相异的的部份,读点东西,再回去找。
- 如果依然不行,放弃使用 stdio 改用 sysread。
学习 Perl
然後重写。说真的,没有简单的转换方式。用 shell 做起来很笨的工
作可以用 Perl 很轻松的做到,而就是这些麻烦之处使得 shell->perl
转换程式
非常不可能写得出来。在重新撰写程式的过程里,你会认清自己真正要做的工作为
何,也希望能够跳脱 shell 的管线资料流机制 [pipeline datastream paradigm],
这东西虽对某些事情很方便,但也常造成低效率。
试试 Net::FTP、TCP::Client 和 NET::Telnet 模组(CPAN
有)。 http://www.perl.com/CPAN/scripts/netstuff/telnet.emul.shar
也有助於模拟 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;
很久很久以前,有个叫做 chat2.pl 的程式库(perl
标准配备之一),但一直没 真正完工。现在,你的最佳选择就是从
CPAN 来的 Comm.pl 程式库。
首先要注意的是,如果你的目的是为了安全(例如避免人们偷看到密码),那你应
该重写你的程式,把重要的资讯从参数中剔除。光是隐藏起来不会让你的程式变得
完全安全。
如要真的把看得见的指令列改掉,你可以设定
$0 这个变数值,如同 perlvar
里写的。但这方法并非各种作业系统都适用。像 sendmail之类的背景程式
(daemons) 就将它们的状态放在那儿:
$0 = "orcus [accepting connections]";
- Unix
- 严格的说起来,这是做不到的-一个 script
的执行是从启动它的 shell 生出一
个不同的程序来执行。这个程序的任何变动不会反映到它的父程序,只会反映到更
改之後它自己创造出来的子程序。有个 shell 魔术可以让你藉着在
shell 里
eval()你 script 的输出来装出这种效果,在
comp.unix.questions FAQ 里有详 细内容。
- VMS
- 对
%ENV 的更改会持续到 Perl
离开,但是目录更动则不会。
假设你的系统支援这种功能,那就只要送个适当的讯号给此程序(参看
kill)。通常是先送一个
TERM 讯号,等一下下,然後再送个 KILL 讯号去终结它。
如果你所指的是离线的程序(未与 tty
连线者),那下列的程序据说在大部份的 Unix系统都能用。非 Unix
系统的使用者应该检查 Your_OS::Process 模组看看有
没有其他的解决方案。
- 打开 /dev/tty 然後对它用 TIOCNOTTY ioctl。请参考
tty(4) 。
- 把目录换到 /
- 重开 STDIN、STDOUT 和 STDERR 使它们不会与旧的
tty 连接。
- 用下列方法把程式丢到背景:
fork && exit;
参看 eg/nih 这 script(perl
原始码发行的一部分)。
问得好。有的时候 -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";
}
如同 Signals 和
Camel 书第六章里所描述的,用 alarm() 函数,
或许再配合上一个讯号处理器。你也可以改用 CPAN 里更具弹性的
Sys::AlarmCall 模组来做。
使用 CPAN 里的 BSD::Resource 模组。
使用 Signals
里面叫 reaper 的程式码,在接到 SIGCHLD 时会呼 叫wait(),或是用 fork
里面写的双 fork 技巧。
有几个连接 SQL 资料库非常好的界面。参看 http://www.perl.com/CPAN/modules/dbperl/DBD
里的 DBD::* 模组。
做不到。你需要摹仿 system()
呼叫(参看 perlipc
里的范例程式),然後设计一个讯号处理器,让它把 INT
讯号传给子程序。
如果你有幸使用到支援非阻挡性读取的系统(大部份
Unix 般的系统都有支援), 你只需要用 Fcntl 模组里的 O_NDELAY 或
O_NONBLOCK 旗标,配合 sysopen():
use Fcntl;
sysopen(FH, "/tmp/somefile", O_WRONLY|O_NDELAY|O_CREAT, 0644)
or die "can't open /tmp/somefile: $!";
最简单的方法就是让 CPAN
这个模组替你代劳。这个模组包含在 5.004及以後的版
本中。如要手动安装 CPAN 模组,或是任何按规矩发展的 CPAN模组,遵循以下步
骤:
- 把原始码解开放到一个暂存区域
perl Makefile.PL
make
make test
make install
如果你用的 perl
版本在编译时没有建入动态连结的功能,那你只消把第叁步 (make)换成
make perl 然後你就会得到一个新的 perl
执行档,里头连 有你新加入的延伸。
在 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 里面的。
#!/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
译者:陈彦铭、萧百龄
中译版着作权所有:陈彦铭、萧百龄及两只老虎工作室。本中译版遵守并使用与原
文版相同的使用条款发行。
|