📄 perlfaq8.html
字号:
<H2><A NAME="_p_q_p_H">如何测量小於一秒的时间?</A></H2>一般来说,你可能做不到。 Time::HiRes 模组(CPAN 有)在某些系统上能达到此 功能。<P>总之,你可能做不到。但是如果你的 Perl 支援 <CODE>syscall()</CODE>函数并支援类似 <CODE>gettimeofday(2)</CODE> 的系统呼叫,你也许可以这麽做:<P><PRE> require 'sys/syscall.ph';</PRE><P><PRE> $TIMEVAL_T = "LL";</PRE><P><PRE> $done = $start = pack($TIMEVAL_T, ());</PRE><P><PRE> syscall( &SYS_gettimeofday, $start, 0)) != -1 or die "gettimeofday: $!";</PRE><P><PRE> ########################## # 在这做你要做的事 # ##########################</PRE><P><PRE> syscall( &SYS_gettimeofday, $done, 0) != -1 or die "gettimeofday: $!";</PRE><P><PRE> @start = unpack($TIMEVAL_T, $start); @done = unpack($TIMEVAL_T, $done);</PRE><P><PRE> # fix microseconds for ($done[1], $start[1]) { $_ /= 1_000_000 }</PRE><P><PRE> $delta_time = sprintf "%.4f", ($done[0] + $done[1] ) - ($start[0] + $start[1] );</PRE><P><P><HR><H2><A NAME="_p_atexit_setjmp_long">如何做 atexit() 或 setjmp()/longjmp() 的动作?(例外处理)</A></H2>第五版的 Perl 增加了 <FONT SIZE=-1>END</FONT> 区块,可以用来模拟 <CODE>atexit()的效果。当程式或执行</CODE> 绪(thread) 终了时就会去呼叫该包装的 <FONT SIZE=-1>END</FONT> 区块(参考 <A HREF="../../tppmsgs/msgs0.htm#60" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlmod.html#">perlmod</A> 文件)。但 是如果当程式被没有抓到的讯号终结了,END 区块就不会被呼叫到,所以当你用 <FONT SIZE=-1>END</FONT> 时应再加上<P><PRE> use sigtrap qw(die normal-signals);</PRE><P>Perl 的例外处理机制就是它的 <CODE>eval()</CODE> 运算子。你可以把 <CODE>eval()</CODE> 当做 setjmp 而die()当做 longjmp 来使用。更详细的说明请参考 <A HREF="../../tppmsgs/msgs0.htm#28" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlipc.html#Signals">Signals</A> 和 Camel书第六章里关於讯号的那段,尤其是描述有关<CODE>flock()</CODE> 的逾时处理器那段。<P>如果你只对例外处理的部分有兴趣,试试 exceptions.pl 程式库(包含在标准perl里)。<P>如果你要的是 <CODE>atexit()</CODE> 语法(以及 <CODE>rmexit()),试试</CODE> <FONT SIZE=-1>CPAN</FONT> 里的 AtExit 模组。<P><P><HR><H2><A NAME="_sockets_b_System_V">为何我的 sockets 程式在 System V (Solaris) 系统下不能用?「不支援本协定」这个错误讯息又是什麽意思?</A></H2>有些 Sys-V 根底的系统,特别像 Solaris 2.X,已重新将一些标准的 socket常数 定义过了。由於这些常数在各种架构下都是定值,所以在 perl程式码中常被人写 死在里面。处理此问题的适当方式 是用 ``use Socket'' 来取得正确的值。<P>须注意尽管 SunOS 和 Solaris 在二进位执行档上相容,这些值是相异的。自己去 想为什麽吧。<P><P><HR><H2><A NAME="_p_q_Perl_I_s_t_W_S_C">如何从 Perl 里呼叫系统中独特的 C 函数?</A></H2>通常是写个外部的模组来处理 - 参看「我要如何学到将 <FONT SIZE=-1>C</FONT> 与 Perl 连结在一起? [h2xs, xsubpp]」 这问题的答案。然而,如果此函数是个系统呼叫,而你的系统 有支援 <CODE>syscall(),那麽可以用</CODE> syscall 函数(说明在 <A HREF="../../tppmsgs/msgs0.htm#68" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlfunc.html#">perlfunc</A> 里)。<P>切记先查查看你的 perl 版本中所附的模组以及 <FONT SIZE=-1>CPAN</FONT> 里的模组,因为也许某人已 经写了个这样的模组。<P><P><HR><H2><A NAME="_b_i_H_J_ioctl_">在哪里可以找引入档来做 ioctl() 或 syscall()?</A></H2>以前这些档案会由标准 perl 发行中所附的 h2ph 工具来产生。这个程式将 <FONT SIZE=-1>C</FONT> 标 头档案里的 <CODE>cpp(1)指令转换成内含副程式定义的档案,像</CODE> &SYS_getitimer,你可 以把它当做函数的参数。这样做并不怎麽完美,但通常可达成任务。简单的像 <EM>errno.h</EM>、<EM>syscall.h</EM> 和<EM>socket.h</EM> 这些档案都没问题,但像 <EM>ioctl.h</EM>这种较难的档案总是需要人工编辑。以下是安装 *.ph 档案的步骤:<P><PRE> 1. 进入最高使用者帐户 2. cd /usr/include 3. h2ph *.h */*.h</PRE><P>如果你的系统支援动态载入,那麽为了可携性、而且合理的做法是使用 h2xs(也 是 perl的标准配备)。这个工具将 <FONT SIZE=-1>C</FONT> 标头档案转换成 Perl 的衍伸档案 (extensions)。 h2xs 的入门要看 <A HREF="../../tppmsgs/msgs0.htm#71" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlxstut.html#">perlxstut</A>。<P>如果你的系统不支援动态载入,你可能仍应使用 h2xs。参看 <A HREF="../../tppmsgs/msgs0.htm#71" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlxstut.html#">perlxstut</A> 和<A HREF="../../tppmsgs/msgs0.htm#98" tppabs="http://www.perl.org/CPAN/doc/manual/html/lib/ExtUtils/MakeMaker.html">MakeMaker</A>(简单来说,就是用 <STRONG>make perl</STRONG>、而非 <STRONG>make</STRONG>来重 建一份使用新的静态连结的 perl)。<P><P><HR><H2><A NAME="_setuid_perl_">为何 setuid perl 程式会抱怨关於系统核心的问题?</A></H2>有些作业系统的核心有臭虫使得 setuid 程式在先天上就不安全。Perl提供你一些方法(在 <A HREF="../../tppmsgs/msgs1.htm#102" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlsec.html#">perlsec</A> 里有写)可跳过这些系统的缺陷。<P><P><HR><H2><A NAME="_p_Y_J_J_S_X_">如何打开对某程式既输入又输出的管道 (pipe)?</A></H2>IPC::Open2 模组(perl 的标准配件)是个好用的方法,它在内部是藉着pipe()、 <CODE>fork()</CODE> 和 <CODE>exec()</CODE> 来完成此工作。不过切记要读它文件里关於锁死的警告 (<A HREF="../../tppmsgs/msgs1.htm#118" tppabs="http://www.perl.org/CPAN/doc/manual/html/lib/IPC/Open2.html">Open2</A>)。<P><P><HR><H2><A NAME="_system_o_o_O">为何用 system() 却得不到一个指令的输出呢?</A></H2>你把 <CODE>system()</CODE> 和反向引号 (``) 的用法搞混了。 <CODE>system()</CODE> 会执行一个指令然後 传回指令结束时的状况资讯(以一个 16 进位值表示:低位元是程序中止所收到的 讯号,高位元才是真正离开时的传回值)。反向引号 (``) 执行一个指令并且把它 所送出的东西送到 STDOUT。<P><PRE> $exit_status = system("mail-users"); $output_string = `ls`;</PRE><P><P><HR><H2><A NAME="_p_O_STDERR_H">如何补捉外部指令的 STDERR?</A></H2>有叁种基本方式执行外部指令:<P><PRE> system $cmd; # 使用 system() $output = `$cmd`; # 使用 反向引号 (``) open (PIPE, "cmd |"); # 使用 open()</PRE><P>在 <CODE>system()</CODE> 下,STDOUT 和 <FONT SIZE=-1>STDERR</FONT> 都会输出到和 script 本身的 <FONT SIZE=-1>STDOUT,</FONT> STDERR相同的出处,除非指令本身将它们导向它处。反向引号和 <CODE>open()</CODE> 则 <STRONG>只</STRONG>读取指令的 <FONT SIZE=-1>STDOUT</FONT> 部份。<P>在上述方法中,你可以在呼叫前更改档案描述元 (file descriptor) 名称:<P><PRE> open(STDOUT, ">logfile"); system("ls");</PRE><P>或者使用 Bourne shell 的档案描述元重导功能:<P><PRE> $output = `$cmd 2>some_file`; open (PIPE, "cmd 2>some_file |");</PRE><P>也可以用档案描述元重导功能将 <FONT SIZE=-1>STDERR</FONT> 导向到 STDOUT:<P><PRE> $output = `$cmd 2>&1`; open (PIPE, "cmd 2>&1 |");</PRE><P>注意你 <EM>不能</EM> 光是将 <FONT SIZE=-1>STDERR</FONT> 开成 <FONT SIZE=-1>STDOUT</FONT> 的复制,而不呼叫 shell来做这个 重导的工作。这样是不行的:<P><PRE> open(STDERR, ">&STDOUT"); $alloutput = `cmd args`; # stderr 仍然会跑掉</PRE><P>失败的原因是,open() 让 <FONT SIZE=-1>STDERR</FONT> 在呼叫 <CODE>open()</CODE> 时往 STDOUT的方向走。然後反 向引号让 STDOUT的内容跑到一个字串变数里,但是没有改变 <FONT SIZE=-1>STDERR</FONT> 的去向(它 仍然往旧的 STDOUT那里跑)。<P>注意,在反向引号里你 <EM>必须</EM> 使用 Bourne shell (sh(1)) 重导的语法而非 <CODE>csh(1)的!至於为何</CODE>Perl 的 <CODE>system()、反向引号和开管道都用</CODE> Bourne shell语法的原因,可在下址找到: <AHREF="../../tppmsgs/msgs1.htm#119" tppabs="http://www.perl.com/CPAN/doc/FMTEYEWTK/versus/csh.whynot">http://www.perl.com/CPAN/doc/FMTEYEWTK/versus/csh.whynot</A><P>你也可以使用 IPC::Open3 模组(perl 标准配备),但注意它的参数顺序和IPC::Open2不一样(参看 <A HREF="../../tppmsgs/msgs1.htm#120" tppabs="http://www.perl.org/CPAN/doc/manual/html/lib/IPC/Open3.html">Open3</A>)。<P><P><HR><H2><A NAME="_D_open_">为何当管道开启失败时 open() 不会传回错误讯息?</A></H2>其实会,只是或许并非以你期望的方式。在遵循标准 <CODE>fork()/exec()</CODE> 机制的系统 上(例如,Unix),运作原理是这样的:open() 导致一个 <CODE>fork()。在父程序里,</CODE> <CODE>open()传回子程序的ID。然後子程序</CODE> <CODE>exec()</CODE> 从管道传来/出 的指令。父程序无 法得知 <CODE>exec()</CODE> 的动作成功与否-它能传回的只有 <CODE>fork()</CODE> 动作成功与否的消息。 要找出这指令是否顺利执行,你得补捉 SIGCHLD讯号并 <CODE>wait()</CODE> 以得到子程序离开 时的状态。如果你要写资料到子程序,则 SIGPIPE也该一并捕捉-否则在你写入之 前可能无法察觉 <CODE>exec()</CODE> 动作已失败了。这些在<A HREF="../../tppmsgs/msgs0.htm#28" tppabs="http://www.perl.org/CPAN/doc/manual/html/pod/perlipc.html#">perlipc</A> 文件里都有说明。<P>在使用 <CODE>spawn()</CODE> 机制的系统里,open() <EM>也许</EM> 能达到你所期望的-除非 perl 使用一个 shell 来起始你的指令。在这情况下以上对 <CODE>fork()/exec()</CODE> 的描述仍适 用。<P><P><HR><H2><A NAME="_b_X_O_V_">在输出值是空的情境里使用反向引号有何不对?</A></H2>严格说起来,没啥不对。但从程式写作严谨与否来说,这样无法写出较易维护的程式码,因为反向引号有一个(可能很巨大的)传回值,而你却忽略它。同时这也是缺乏效率的方法,因为你得把每行所有的输出读进来、留一块记忆体给它们,然後再把它们丢开。人们常常做下列这种事:<P><PRE> `cp file file.bak`;</PRE><P>然後它们就会想:「嘿,乾脆以後都用反向引号来执行程式好了。」这是馊主意, 因为反向引号的目的在补捉程式的输出;system() 函数才是用来执行程式的。<P>再看看下列这一行:<P><PRE> `cat /etc/termcap`;</PRE><P>你还没有指定输出,所以它会浪费记忆体(就那麽一下子)。另外你也忘了检查 <CODE>$?</CODE> 看看程式是否正确的执行。即使你写成<P><PRE> print `cat /etc/termcap`;</PRE><P>但在大部份情况下,这本来可以、而且也应该写成<P><PRE> system("cat /etc/termcap") == 0 or die "cat program failed!";</PRE><P>这样可快速地得到输出(一产生出来就会得到,不用等到最後),并且检查传回值。<P><CODE>system()</CODE> 同时具有直接决定是否先做 shell 万用字元 (wildcard)处理的功能, 反向引号就不行。<P><P><HR><H2><A NAME="_p_g_L_shell_B_z_I_s_V_">如何不经过 shell 处理来呼叫反向引号?</A></H2>这需要些技巧。本来是写成<P><PRE> @ok = `grep @opts '$search_string' @filenames`;</PRE><P>你得改成:<P><PRE> my @ok = (); if (open(GREP, "-|")) { while (<GREP>) { chomp; push(@ok, $_); } close GREP; } else { exec 'grep', @opts, $search_string, @filenames; }</PRE><P>一如 <CODE>system(),当你</CODE> <CODE>exec()</CODE> 一个序列时不会有 shell 解译的情况发生。<P><P><HR><H2><A NAME="_F_EOF_Unix_W_O_D_AMS_D">为何给了 EOF(Unix 上是 ^D,MS-DOS 上是 ^Z)後我的程式就不能从 STDIN 读取东西了呢?</A></H2>因为某些 stdio 的 set error 和 eof 旗标需要清除。你可以用 <FONT SIZE=-1>POSIX</FONT> 模组里定 义的clearerr()。这是在技术上正确的解决之道。还有一些较不保险的方法:<P><OL><LI><STRONG><A NAME="item_"></A></STRONG>试着保存搜寻指标然後去找它,例如:<P><PRE> $where = tell(LOG); seek(LOG, $where, 0);</PRE><P><LI><STRONG><A NAME="item_"></A></STRONG>如果那样行不通,试着去 <CODE>seek()</CODE> 档案的另一部份然後再找回来。<P><LI><STRONG><A NAME="item_"></A></STRONG>如果还是行不通,试着 <CODE>seek()</CODE>档案另一个相异的的部份,读点东西,再回去找。<P><LI><STRONG><A NAME="item_"></A></STRONG>如果依然不行,放弃使用 stdio 改用 sysread。<P></OL><P><HR><H2><A NAME="_p_shell_perl_H">如何把 shell 程式转成 perl?</A></H2>学习 Perl 然後重写。说真的,没有简单的转换方式。用 shell 做起来很笨的工 作可以用 Perl 很轻松的做到,而就是这些麻烦之处使得 shell->perl 转换程式 非常不可能写得出来。在重新撰写程式的过程里,你会认清自己真正要做的工作为 何,也希望能够跳脱 shell 的管线资料流机制 [pipeline datastream paradigm], 这东西虽对某些事情很方便,但也常造成低效率。<P><P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -