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 所寫的常見問題集和文件(例如, perlvmsperlplan9,...),以取得 perl 在個別差異方面更詳盡的資料。


如何得知使用者正在哪個作業系統下執行我的 perl 程式?

$^O 這個變數(若使用 English 模組就是 $OSTYPE)會指出你的 perl 解譯器執 行檔是替哪個作業系統、平台所建的。


為什麼 exec() 不會傳值回來?

因為這正是它所做的:它用另一個不同的程式來取代你當時所執行的。如果你的程 式需要繼續跑下去(這可能正是你問此問題的原因吧?),改用 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() 的逾時處理器的說明,或駱駝書第六 章。


如何更動 Unix 系統上隱式密碼檔 (shadow password) 的內容?

如果你的 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() 或 alarm() 的動作呢?

如果你要比 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] );


如何做 atexit() 或 setjmp()/longjmp() 的動作?(例外處理)

第五版的 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 模組。


為何我的 sockets 程式在 System V (Solaris) 系統下不能用?「不支援本協定」這個錯誤訊息又是什麼意思?

有些 Sys-V 根底的系統,特別像 Solaris 2.X,已重新將一些標準的 socket常數 定義過了。由於這些常數在各種架構下都是定值,所以在 perl程式碼中常被人寫 死在裡面。處理此問題的適當方式 是用 ``use Socket'' 來取得正確的值。

須注意儘管 SunOS 和 Solaris 在二進位執行檔上相容,這些值是相異的。自己去 想為什麼吧。


如何從 Perl 裡呼叫系統中獨特的 C 函數?

通常是寫個外部的模組來處理 - 參看「我要如何學到將 C 與 Perl 連結在一起? [h2xs, xsubpp]」 這問題的答案。然而,如果此函數是個系統呼叫,而你的系統 有支援 syscall(),那麼可以用 syscall 函數(說明在 perlfunc 裡)。

切記先查查看你的 perl 版本中所附的模組以及 CPAN 裡的模組,因為也許某人已 經寫了個這樣的模組。


在哪裡可以找引入檔來做 ioctl() 或 syscall()?

以前這些檔案會由標準 perl 發行中所附的 h2ph 工具來產生。這個程式將 C 標 頭檔案裡的 cpp(1)指令轉換成內含副程式定義的檔案,像 &SYS_getitimer,你可 以把它當做函數的參數。這樣做並不怎麼完美,但通常可達成任務。簡單的像 errno.hsyscall.hsocket.h 這些檔案都沒問題,但像 ioctl.h 這種較難的檔案總是需要人工編輯。以下是安裝 *.ph 檔案的步驟:

    1.  進入最高使用者帳戶
    2.  cd /usr/include
    3.  h2ph *.h */*.h

如果你的系統支援動態載入,那麼為了可攜性、而且合理的做法是使用 h2xs(也 是 perl的標準配備)。這個工具將 C 標頭檔案轉換成 Perl 的衍伸檔案 (extensions)。 h2xs 的入門要看 perlxstut

如果你的系統不支援動態載入,你可能仍應使用 h2xs。參看 perlxstutMakeMaker (簡單來說,就是用 make perl 、而非 make來重 建一份使用新的靜態連結的 perl)。


為何 setuid perl 程式會抱怨關於系統核心的問題?

有些作業系統的核心有臭蟲使得 setuid 程式在先天上就不安全。Perl提供你一些 方法(在 perlsec 裡有寫)可跳過這些系統的缺陷。


如何打開對某程式既輸入又輸出的管道 (pipe)?

IPC::Open2 模組(perl 的標準配件)是個好用的方法,它在內部是藉著pipe()、 fork()exec() 來完成此工作。不過切記要讀它文件裡關於鎖死的警告 ( Open2 )。


為何用 system() 卻得不到一個指令的輸出呢?

你把 system() 和反向引號 (``) 的用法搞混了。 system() 會執行一個指令然後 傳回指令結束時的狀況資訊(以一個 16 進位值表示:低位元是程序中止所收到的 訊號,高位元才是真正離開時的傳回值)。反向引號 (``) 執行一個指令並且把它 所送出的東西送到 STDOUT。

    $exit_status = system("mail-users");
    $output_string = `ls`;


如何補捉外部指令的 STDERR?

有三種基本方式執行外部指令:

    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 )。


為何當管道開啟失敗時 open() 不會傳回錯誤訊息?

其實會,只是或許並非以你期望的方式。在遵循標準 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)處理的功能, 反向引號就不行。


如何不經過 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;
    }

一如 system(),當你 exec() 一個序列時不會有 shell 解譯的情況發生。


為何給了 EOF(Unix 上是 ^D,MS-DOS 上是 ^Z)後我的程式就不能從 STDIN 讀取東西了呢?

因為某些 stdio 的 set error 和 eof 旗標需要清除。你可以用 POSIX 模組裡定 義的clearerr()。這是在技術上正確的解決之道。還有一些較不保險的方法:

  1. 試著保存搜尋指標然後去找它,例如:

        $where = tell(LOG);
        seek(LOG, $where, 0);
    

  2. 如果那樣行不通,試著去 seek() 檔案的另一部份然後再找回來。

  3. 如果還是行不通,試著 seek() 檔案另一個相異的的部份,讀點東西,再回去找。

  4. 如果依然不行,放棄使用 stdio 改用 sysread。


如何把 shell 程式轉成 perl?

學習 Perl 然後重寫。說真的,沒有簡單的轉換方式。用 shell 做起來很笨的工 作可以用 Perl 很輕鬆的做到,而就是這些麻煩之處使得 shell->perl 轉換程式 非常不可能寫得出來。在重新撰寫程式的過程裡,你會認清自己真正要做的工作為 何,也希望能夠跳脫 shell 的管線資料流機制 [pipeline datastream paradigm], 這東西雖對某些事情很方便,但也常造成低效率。


perl 能處理 telnet 或 ftp 這種雙向互動嗎?

試試 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;


如何在 Perl 裡達到 Expect 的功能?

很久很久以前,有個叫做 chat2.pl 的程式庫(perl 標準配備之一),但一直沒 真正完工。現在,你的最佳選擇就是從 CPAN 來的 Comm.pl 程式庫。


有沒有可能將 perl 的指令列隱藏起來,以躲避像 "ps" 之類的程式?

首先要注意的是,如果你的目的是為了安全(例如避免人們偷看到密碼),那你應 該重寫你的程式,把重要的資訊從參數中剔除。光是隱藏起來不會讓你的程式變得 完全安全。

如要真的把看得見的指令列改掉,你可以設定 $0 這個變數值,如同 perlvar 裡寫的。但這方法並非各種作業系統都適用。像 sendmail之類的背景程式 (daemons) 就將它們的狀態放在那兒:

    $0 = "orcus [accepting connections]";


我在 perl script 裡 {更動目錄,更改我的使用環境}。為何這些改變在程式執行完後就消失了呢?如何讓我做的修改顯露出來?

Unix
嚴格的說起來,這是做不到的-一個 script 的執行是從啟動它的 shell 生出一 個不同的程序來執行。這個程序的任何變動不會反映到它的父程序,只會反映到更 改之後它自己創造出來的子程序。有個 shell 魔術可以讓你藉著在 shell 裡 eval()你 script 的輸出來裝出這種效果,在 comp.unix.questions FAQ 裡有詳 細內容。

VMS
%ENV 的更改會持續到 Perl 離開,但是目錄更動則不會。


如何關閉一個程序的檔案把手而不用等它完成呢?

假設你的系統支援這種功能,那就只要送個適當的訊號給此程序(參看 kill)。通常是先送一個 TERM 訊號,等一下下,然後再送個 KILL 訊號去終結它。


如何 fork 出一個背景執行 (daemon) 程序?

如果你所指的是離線的程序(未與 tty 連線者),那下列的程序據說在大部份的 Unix系統都能用。非 Unix 系統的使用者應該檢查 Your_OS::Process 模組看看有 沒有其他的解決方案。


如何使我的程式和 sh 及 csh 一起執行?

參看 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 模組來做。


如何設定 CPU 使用限制?

使用 CPAN 裡的 BSD::Resource 模組。


在 Unix 系統上如何避免產生僵屍程序 (zombies)?

使用 Signals 裡面叫 reaper 的程式碼,在接到 SIGCHLD 時會呼 叫wait(),或是用 fork 裡面寫的雙 fork 技巧。


如何使用一個 SQL 資料庫?

有幾個連接 SQL 資料庫非常好的界面。參看 http://www.perl.com/CPAN/modules/dbperl/DBD 裡的 DBD::* 模組。


如何讓 system() 在收到 control-C 後就離開?

做不到。你需要摹仿 system() 呼叫(參看 perlipc 裡的範例程式),然後設計一個訊號處理器,讓它把 INT 訊號傳給子程序。


如何開啟一個檔案但不阻擋其他程序的閱讀?

如果你有幸使用到支援非阻擋性讀取的系統(大部份 Unix 般的系統都有支援), 你只需要用 Fcntl 模組裡的 O_NDELAYO_NONBLOCK 旗標,配合 sysopen():

    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模組,遵循以下步 驟:

  1. 把原始碼解開放到一個暫存區域

  2.     perl Makefile.PL
    

  3.     make
    

  4.     make test
    

  5.     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 裡面的。


如何從終端機一次抓進一個按鍵?如果用 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

譯者:陳彥銘、蕭百齡

中譯版著作權所有:陳彥銘、蕭百齡及兩隻老虎工作室。本中譯版遵守並使用與原 文版相同的使用條款發行。