Scroll to navigation

PERLFAQ8(7) Perl Programmers Reference Guide PERLFAQ8(7)

NAME

perlfaq8 - 系統互動 (2003/01/26 17:44:04 )

DESCRIPTION 描述

Perl FAQ 的這一節覆蓋了與系統互動有關的問題。主題包括程序間通訊 (IPC),使用者介面控制 (鍵盤,螢幕和指點裝置),以及其他與資料操作不相關的事項

閱讀你係統中的 perl 自帶的 FAQ 和文件 (例如,perlvms,perlplan9...)。它們會包含有關你的 perl 版本的更詳細的資訊。

如何找出正在執行的作業系統?

為什麼 exec() 不返回?

因為這正是它所做的:它用另一個不同的程式來取代你當時所執行的。如果你的程 式需要繼續跑下去(這可能正是你問此問題的原因吧?),改用 system() 。

如何對鍵盤/螢幕/滑鼠做些花樣?

連線/控制 鍵盤、螢幕和指標裝置(「滑鼠」)的方法因作業系統的不同而有不 同;不妨試試下列模組:

    Term::Cap                   標準內建模組
    Term::ReadKey               CPAN
    Term::ReadLine::Gnu         CPAN
    Term::ReadLine::Perl        CPAN
    Term::Screen                CPAN
    
    Term::Cap                   標準內建模組
    Curses                      CPAN
    Term::ANSIColor             CPAN
    
    Tk                          CPAN
    

Some of these specific cases are shown below.

How do I print something out in color?

In general, you don't, because you don't know whether the recipient has a color-aware display device. If you know that they have an ANSI terminal that understands color, you can use the Term::ANSIColor module from CPAN:

    use Term::ANSIColor;
    print color("red"), "Stop!\n", color("reset");
    print color("green"), "Go!\n", color("reset");

Or like this:

    use Term::ANSIColor qw(:constants);
    print RED, "Stop!\n", RESET;
    print GREEN, "Go!\n", RESET;

How do I read just one key without waiting for a return key?

Controlling input buffering is a remarkably system-dependent matter. On many systems, you can just use the stty command as shown in "getc" in perlfunc, but as you see, that's already getting you into portability snags.

    open(TTY, "+</dev/tty") or die "no tty: $!";
    system "stty  cbreak </dev/tty >/dev/tty 2>&1";
    $key = getc(TTY);           # perhaps this works
    # OR ELSE
    sysread(TTY, $key, 1);      # probably this does
    system "stty -cbreak </dev/tty >/dev/tty 2>&1";

The Term::ReadKey module from CPAN offers an easy-to-use interface that should be more efficient than shelling out to stty for each key. It even includes limited support for Windows.

    use Term::ReadKey;
    ReadMode('cbreak');
    $key = ReadKey(0);
    ReadMode('normal');

However, using the code requires that you have a working C compiler and can use it to build and install a CPAN module. Here's a solution using the standard POSIX module, which is already on your systems (assuming your system supports POSIX).

    use HotKey;
    $key = readkey();

And here's the HotKey module, which hides the somewhat mystifying calls to manipulate the POSIX termios structures.

    # HotKey.pm
    package HotKey;

    @ISA = qw(Exporter);
    @EXPORT = qw(cbreak cooked readkey);

    use strict;
    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);  # ok, so i don't want echo either
        $term->setcc(VTIME, 1);
        $term->setattr($fd_stdin, TCSANOW);
    }

    sub cooked {
        $term->setlflag($oterm);
        $term->setcc(VTIME, 0);
        $term->setattr($fd_stdin, TCSANOW);
    }

    sub readkey {
        my $key = '';
        cbreak();
        sysread(STDIN, $key, 1);
        cooked();
        return $key;
    }

    END { cooked() }

    1;

How do I check whether input is ready on the keyboard?

The easiest way to do this is to read a key in nonblocking mode with the Term::ReadKey module from CPAN, passing it an argument of -1 to indicate not to block:

    use Term::ReadKey;

    ReadMode('cbreak');

    if (defined ($char = ReadKey(-1)) ) {
        # input was waiting and it was $char
    } else {
        # no input was waiting
    }

    ReadMode('normal');                  # restore normal tty settings

How do I clear the screen?

If you only have do so infrequently, use "system":

    system("clear");

If you have to do this a lot, save the clear string so you can print it 100 times without calling a program 100 times:

    $clear_string = `clear`;
    print $clear_string;

If you're planning on doing other screen manipulations, like cursor positions, etc, you might wish to use Term::Cap module:

    use Term::Cap;
    $terminal = Term::Cap->Tgetent( {OSPEED => 9600} );
    $clear_string = $terminal->Tputs('cl');

How do I get the screen size?

If you have Term::ReadKey module installed from CPAN, you can use it to fetch the width and height in characters and in pixels:

    use Term::ReadKey;
    ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();

This is more portable than the raw "ioctl", but not as illustrative:

    require 'sys/ioctl.ph';
    die "no TIOCGWINSZ " unless defined &TIOCGWINSZ;
    open(TTY, "+</dev/tty")                     or die "No tty: $!";
    unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) {
        die sprintf "$0: ioctl TIOCGWINSZ (%08x: $!)\n", &TIOCGWINSZ;
    }
    ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
    print "(row,col) = ($row,$col)";
    print "  (xpixel,ypixel) = ($xpixel,$ypixel)" if $xpixel ⎪⎪ $ypixel;
    print "\n";

如何向使用者詢問密碼?

(這個問題跟全球資訊網一點關係也沒有。如果你要找的是跟 WWW 有關的,那就 看另一份常見問題集吧。)

在 perlfunc 中的 "crypt" 裡面有個範例。首先,將你的終端機設為「無回應」"no echo" 模式,然後就用平常的方法將密碼讀入。你可以用老式的 ioctl() 函式、 POSIX 終端機控制函式(參看 POSIX ,和駱駝書第七章),或是呼叫 stty 程式,這些方法的可攜性/移植性程度都不一樣。

你也可以在大部份系統上使用 CPAN 裡的 Term::ReadKey 模組,這個模組較易使用而且理論上也較據可攜性/移植性。

    use Term::ReadKey;

    ReadMode('noecho');
    $password = ReadLine(0);

如何讀寫串列埠?

這端看你在什麼作業系統上執行你的程式。以 Unix 來說,序列埠可以透過 /dev 目錄下的檔案來擷取; 而在其他系統上,裝置的名稱無疑地會不一樣。以下是一些在裝置互動時可能遭遇的共同問題:

你的系統可能會使用鎖檔來控制多重讀寫的情況。確定你用的是正確的協定。因為當多個程式同時對一個裝置做讀取時可能會發生意想不到的情況。
如果你打算對一個裝置同時做讀與寫的動作,你得將它開到更新的模式( 在 perlfunc 中的 open 裡有更詳細的解說)。如果你不希望冒著阻擋其他程式讀取 這個裝置的風險,那就得用 sysopen() 和 Fcntl 模組(標準 perl 的一部分)內 的 "O_RDWR⎪O_NDELAY⎪O_NOCTTY"。在 perlfunc 中的 sysopen 裡有對此方法更 詳盡的解說。
有些裝置會等著在每行結尾處看到一個 "\r",而非 "\n"。在某些平臺上的 perl, "\r"和 "\n" 與它們平常(在 Unix 上)所指的 ASCII 值 "\015" 和 "\012" 有 所不同。你也許得直接給定數值,例如用八進位 ("\015")、十六進位 ("0x0D"), 或指定控制字元 ("\cM")。

    print DEV "atv1\012";       # wrong, for some devices
    print DEV "atv1\015";       # right, for some devices
    

儘管對普通的文字檔案,一個 "\n" 便可解決斷行的問題,但目前在不同作業系統 間(Unix、DOS/Win 和 Macintosh),對於斷行記號仍無統一標準,而只有用 "\015\012" 來當成 每行的結尾,然後再視需要去掉輸出中不想要的部份。這 個做法尤其常用於 socket輸出/輸入 與自動重新整理 (autoflushing),也是接下來 要討論的主題。

如果你希望 print() 的時候每個字元都要送到你指定的裝置去,那你應自動重新整理檔案控制代碼。可以使用 select()$⎪ 變數控制自動重新整理,參見 perlvar 中的 "$⎪" 和 perlfunc 中的 "select",或 perlfaq5, ``How do I flush/unbuffer an output filehandle? Why must I do this?''):

    $oldh = select(DEV);
    $⎪ = 1;
    select($oldh);
    

你也可能看到不使用額外的暫存變數的寫法,例如:

    select((select(DEV), $⎪ = 1)[0]);
    

Or if you don't mind pulling in a few thousand lines of code just because you're afraid of a little $⎪ variable:

    use IO::Handle;
    DEV->autoflush(1);
    

As mentioned in the previous item, this still doesn't work when using socket I/O between Unix and Macintosh. You'll need to hard code your line terminators, in that case.

如果你正在做一個阻塞的 read() 或 sysread() 動作,則你需要安排一個鬧 鈴把手或提供一個逾時設定(參看 alarm)。如果你是用非阻擋式的 開檔,那麼就要配合非阻擋性的讀取,也就是說得用到4 個引數的 select() 來確 定此裝置的 輸出/入 是否已準備好了(參考 perlfunc 中的 select )。

While trying to read from his caller-id box, the notorious Jamie Zawinski <jwz@netscape.com>, after much gnashing of teeth and fighting with sysread, sysopen, POSIX's tcgetattr business, and various other functions that go bump in the night, finally came up with this:

    sub open_modem {
        use IPC::Open2;
        my $stty = `/bin/stty -g`;
        open2( \*MODEM_IN, \*MODEM_OUT, "cu -l$modem_device -s2400 2>&1");
        # starting cu hoses /dev/tty's stty settings, even when it has
        # been opened on a pipe...
        system("/bin/stty $stty");
        $_ = <MODEM_IN>;
        chomp;
        if ( !m/^Connected/ ) {
            print STDERR "$0: cu printed `$_' instead of `Connected'\n";
        }
    }

如何解碼加密的口令檔案?

花大把大把的錢去買破解專用的硬體,這會讓你成為焦點話題。

說正經的,如果是碰到 Unix 密碼檔的話就不行 - Unix 密碼系統用的是單向的加 密函式。像 Crack 之類的程式可以暴力地(並聰明地)試著猜出密碼,但無法 (也不能)保證速戰速決。

如果你耽心的是使用者選取不良的密碼,你應該在使用者換密碼時主動稽核(例如說修改 passwd(1) 程式加入這個功能)。

如何在後臺開啟程序?

Several modules can start other processes that do not block your Perl program. You can use IPC::Open3, Parallel::Jobs, IPC::Run, and some of the POE modules. See CPAN for more details.

你可以使用:

    system("cmd &")

或是用 fork,像 perlfunc 中的 fork 裡寫的(在 perlipc 裡有更進一步的 範例)。如果你在 Unix 類的系統上的話,請注意以下幾件事情:

主程式和背景程式(即「子」程式)共用同一個 STDIN、STDOUT 和 STDERR 檔案 把手。如果兩個程式想同時去讀、寫同一個檔案把手,就可能有怪事會發生。你也 許應該替子程式關閉或重新開啟這些把手。你可以用開啟一個管道 (pipe) 的方法 避免這些問題(參看 open)但是在某些系統上這樣做會強迫子程式 必須比父程式早死。
訊號
SIGCHLD、可能還有 SIGPIPE 這兩個訊號要抓到。當背景程式執行完成後就會送出 SIGCHLD 訊號。而當你寫入一個子程式已經關閉的檔案把手時就會收到 SIGPIPE 訊號(一個未抓住的 SIGPIPE 可能導致你的程式無聲無息地死去)。用 system("cmd&") 的話不會有這樣的問題。
殭屍程序
你得做準備,在子程式結束時「收成」它:

    $SIG{CHLD} = sub { wait };
    

    $SIG{CHLD} = 'IGNORE';
    

You can also use a double fork. You immediately wait() for your first child, and the init daemon will wait() for your grandchild once it exits.

        unless ($pid = fork) {
                unless (fork) {
            exec "what you really wanna do";
            die "exec failed!";
                }
        exit 0;
        }
    waitpid($pid,0);
    

在 Signals 有範例程式教你怎麼做。用 system("prog &") 的 話不會有僵 程式的問題。

如何截獲控制字元/訊號?

你並不能真的 ``捕捉'' 一個控制字元。而是控制字元產生一個訊號讓你捕捉。關於訊號的資料可以在 Signals 以及駱駝書第六章裡找到。

要小心的是,大多 C 程式庫無法重新進入 [re-entrant]。因此當你要嘗試著在一 個處理器裡做 print() 動作,而這個處理器是由另一個stdio 的動作所叫出來的 話,你的內部結構可能會處於失調狀態,而程式可能會丟出記憶核心 (dump core)。 有的時候你可以用 syswrite() 取代 print() 以避免這個狀況。

除非你極為小心,否則在一個訊號處理器中,唯一安全可做的是:設定一個變數後離開。而在第一個情況下,你在設定變數的時候應確定 malloc() 不會被叫出來 (譬如,設定一個已經有值的變數)。

例如:

    $Interrupted = 0;   # 確定它有個值
    $SIG{INT} = sub {
        $Interrupted++;
        syswrite(STDERR, "ouch\n", 5);
    }

然而,因為系統呼叫會自己重新啟動,你將會發現如果你用的是「慢的」呼叫,像 < FH>、read()、connect() 或 wait(),那麼將它們停下的唯一辦法是使 用「跳遠」的方式跳出來;也就是產生一個例外訊號。參看在 Signals 裡對阻擋性 flock() 的逾時處理器的說明,或駱駝書第六 章。

在 Unix 系統中如何修改 shadow 檔案?

如果你的 perl 安裝正確的話,在 perlfunc 裡描述的 getpw*() 函式應該就能夠讀取隱式密碼檔了(只有讀取權)。要更動該檔案內容,做一個新的密碼檔(這個檔案的格式因系統而異,請看 passwd(5) )然後用 pwd_mkdb(8)(參考 pwd_mkdb(5))來安裝新的密碼檔。

如何設定時間和日期?

假設你有足夠的許可權,你應該可以用 date(1) 程式來設定系統的時間與日期。 (但沒有針對個別程式修改時間日期的方法)這機制在 Unix、MS-DOS、Windows 和 NT 下都能用;VMS 下則要用 set time 。

然而,如果你只是要更動你的時區,只消設定一個環境變數即可:

    $ENV{TZ} = "MST7MDT";                  # unixish
    $ENV{'SYS$TIMEZONE_DIFFERENTIAL'}="-5" # vms
    system "trn comp.lang.perl.misc";

如何 sleep() 或 alarm() 少於一秒的時間?

如果你要比 sleep() 所提供的最小單位一秒更精細的話,最簡單的方法就是用 select 裡面寫的 select() 函式。試一試 Time::HiRes 和 BSD::Itimer 模組 (可以從 CPAN 下載,從 Perl 5.8 開始 Time::HiRes 成為標準發行的一部分).

如何測度少於一秒的時間?

一般來說,你可能做不到。 Time::HiRes 模組(CPAN 有,從 Perl 5.8 開始成為標準發行的一部分)在某些系統上能達到此 功能。

總之,你可能做不到。但是如果你的 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: $!";

       ##########################
       # DO YOUR OPERATION HERE #
       ##########################

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

For example, you can use this to make sure your filter program managed to finish its output without filling up the disk:

    END {
        close(STDOUT) ⎪⎪ die "stdout close failed: $!";
    }

如果當程式被沒有抓到的訊號終結了,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 裡的模組,因為也許某人已 經寫了個這樣的模組。 On Windows, try Win32::API. On Macs, try Mac::Carbon. If no module has an interface to the C function, you can inline a bit of C in your Perl source with Inline::C.

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

以前這些檔案會由標準 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程式會抱怨關於系統核心的問題?

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

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

IPC::Open2 模組(perl 的標準配件)是個好用的方法,它在內部是藉著pipe()、 fork() 和 exec() 來完成此工作。不過切記要讀它檔案裡關於鎖死的警告 ( 參見 IPC::Open2 )。參見 perlipc 中的 "Bidirectional Communication with Another Process" 和 "Bidirectional Communication with Yourself"

You may also use the IPC::Open3 module (part of the standard perl distribution), but be warned that it has a different order of arguments from IPC::Open2 (see IPC::Open3).

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

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

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

如何捕捉外部指令的 STDERR?

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

    system $cmd;                # 使用 system()
    $output = `$cmd`;           # 使用 backticks (``)
    open (PIPE, "cmd ⎪");       # 使用 open()

在 system() 下,STDOUT 和 STDERR 都會輸出到和 script 本身的 STDOUT, STDERR相同的出處,除非指令本身將它們導向它處。反向引號和 open() 則 只 讀取指令的 STDOUT 部份。

你也可以使用 IPC::Open3 模組. Benjamin Goldberg provides some sample code:

To capture a program's STDOUT, but discard its STDERR:

    use IPC::Open3;
    use File::Spec;
    use Symbol qw(gensym);
    open(NULL, ">", File::Spec->devnull);
    my $pid = open3(gensym, \*PH, ">&NULL", "cmd");
    while( <PH> ) { }
    waitpid($pid, 0);

To capture a program's STDERR, but discard its STDOUT:

    use IPC::Open3;
    use File::Spec;
    use Symbol qw(gensym);
    open(NULL, ">", File::Spec->devnull);
    my $pid = open3(gensym, ">&NULL", \*PH, "cmd");
    while( <PH> ) { }
    waitpid($pid, 0);

To capture a program's STDERR, and let its STDOUT go to our own STDERR:

    use IPC::Open3;
    use Symbol qw(gensym);
    my $pid = open3(gensym, ">&STDERR", \*PH, "cmd");
    while( <PH> ) { }
    waitpid($pid, 0);

To read both a command's STDOUT and its STDERR separately, you can redirect them to temp files, let the command run, then read the temp files:

    use IPC::Open3;
    use Symbol qw(gensym);
    use IO::File;
    local *CATCHOUT = IO::File->new_tempfile;
    local *CATCHERR = IO::File->new_tempfile;
    my $pid = open3(gensym, ">&CATCHOUT", ">&CATCHERR", "cmd");
    waitpid($pid, 0);
    seek $_, 0, 0 for \*CATCHOUT, \*CATCHERR;
    while( <CATCHOUT> ) {}
    while( <CATCHERR> ) {}

But there's no real need for *both* to be tempfiles... the following should work just as well, without deadlocking:

    use IPC::Open3;
    use Symbol qw(gensym);
    use IO::File;
    local *CATCHERR = IO::File->new_tempfile;
    my $pid = open3(gensym, \*CATCHOUT, ">&CATCHERR", "cmd");
    while( <CATCHOUT> ) {}
    waitpid($pid, 0);
    seek CATCHERR, 0, 0;
    while( <CATCHERR> ) {}

And it'll be faster, too, since we can begin processing the program's stdout immediately, rather than waiting for the program to finish.

在上述方法中,你可以在呼叫前更改檔案描述符 (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 still escapes

失敗的原因是,open() 讓 STDERR 在呼叫 open() 時往 STDOUT的方向走。然後反 向引號讓 STDOUT的內容跑到一個字串變數里,但是沒有改變 STDERR 的去向(它 仍然往舊的 STDOUT那裡跑)。

注意,在反向引號裡你 必須 使用 Bourne shell (sh(1)) 重導的語法而非 csh(1)的!至於為何 Perl 的 system()、反向引號和開管道都用 Bourne shell語法的原因,可在下址找到:"Far More Than You Ever Wanted To Know", http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz . 要同時捕捉一個命令的 STDERR 和 STDOUT:

    $output = `cmd 2>&1`;                       # either with backticks
    $pid = open(PH, "cmd 2>&1 ⎪");              # or with an open pipe
    while (<PH>) { }                            #    plus a read

To capture a command's STDOUT but discard its STDERR:

    $output = `cmd 2>/dev/null`;                # either with backticks
    $pid = open(PH, "cmd 2>/dev/null ⎪");       # or with an open pipe
    while (<PH>) { }                            #    plus a read

To capture a command's STDERR but discard its STDOUT:

    $output = `cmd 2>&1 1>/dev/null`;           # either with backticks
    $pid = open(PH, "cmd 2>&1 1>/dev/null ⎪");  # or with an open pipe
    while (<PH>) { }                            #    plus a read

To exchange a command's STDOUT and STDERR in order to capture the STDERR but leave its STDOUT to come out our old STDERR:

    $output = `cmd 3>&1 1>&2 2>&3 3>&-`;        # either with backticks
    $pid = open(PH, "cmd 3>&1 1>&2 2>&3 3>&-⎪");# or with an open pipe
    while (<PH>) { }                            #    plus a read

To read both a command's STDOUT and its STDERR separately, it's easiest and safest to redirect them separately to files, and then read from those files when the program is done:

    system("program args 1>/tmp/program.stdout 2>/tmp/program.stderr");

Ordering is important in all these examples. That's because the shell processes file descriptor redirections in strictly left to right order.

    system("prog args 1>tmpfile 2>&1");
    system("prog args 2>&1 1>tmpfile");

The first command sends both standard out and standard error to the temporary file. The second command sends only the old standard output there, and the old standard error shows up on the old standard out.

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

If the second argument to a piped open() contains shell metacharacters, perl fork()s, then exec()s a shell to decode the metacharacters and eventually run the desired program. If the program couldn't be run, it's the shell that gets the message, not Perl. All your Perl program can find out is whether the shell itself could be successfully started. You can still capture the shell's STDERR and check it for error messages. See "How can I capture STDERR from an external command?" elsewhere in this document, or use the IPC::Open3 module.

If there are no shell metacharacters in the argument of open(), Perl runs the command directly, without using the shell, and can correctly report whether the command started.

在忽略返回值的上下文裡使用反向引號有何不對?

嚴格說起來,沒啥不對。但從程式寫作嚴謹與否來說,這樣無法寫出較易維護的程式碼。Perl 有多種方法可以執行外部命令。反引號只是其中一個;它收集命令的輸出,在程式中加以應用。 "system" 函式是另一個,它不這樣做

Writing backticks in your program sends a clear message to the readers of your code that you wanted to collect the output of the command. Why send a clear message that isn't true?

再看看下列這一行:

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

在 Perl 5.8.0 中,你可以使用有多個引數的 open()。類似 system()exec() 的列表形式,不會進行 shell 轉義。

   open( GREP, "-⎪", 'grep', @opts, $search_string, @filenames );
   chomp(@ok = <GREP>);
   close GREP;

也可以這樣:

    my @ok = ();
    if (open(GREP, "-⎪")) {
        while (<GREP>) {
            chomp;
            push(@ok, $_);
        }
        close GREP;
    } else {
        exec 'grep', @opts, $search_string, @filenames;
    }

一如 system(),當你 exec() 一個序列時不會有 shell 解譯的情況發生。更多示例可以從 perlipc 的 "Safe Pipe Opens" 中找到。

Note that if you're use Microsoft, no solution to this vexing issue is even possible. Even if Perl were to emulate fork(), you'd still be stuck, because Microsoft does not have a argc/argv-style API.

為何給了 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;             # new in 5.004
    $handle = IO::Socket::INET->new('www.perl.com:80')
            ⎪⎪ die "can't connect to port 80 on www.perl.com: $!";
    $handle->autoflush(1);
    if (fork()) {               # XXX: undef means failure
        select($handle);
        print while <STDIN>;    # everything from stdin to socket
    } else {
        print while <$handle>;  # everything from socket to stdout
    }
    close $handle;
    exit;

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

很久很久以前,有個叫做 chat2.pl 的程式庫(perl 標準配備之一),但一直沒 真正完工。如果遇到它的話,不要去用它。現在,你的最佳選擇就是從 CPAN 來的 Expect 模組,同時它需要 CPAN 的另兩個模組, IO::Pty 和 IO::Stty.

有沒有可能將 perl的指令列隱藏起來,以躲避像

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

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

    $0 = "orcus [accepting connections]";

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

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

如何關閉一個程式的檔案控制代碼而不用等它完成呢?

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

如何 fork 一個守護程序?

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

  • 開啟 /dev/tty 然後對它用 TIOCNOTTY ioctl。請參考 tty(4) 。更好的辦法,你可以只用 POSIX::setsid() 函式,從而不必擔心程序組。
  • 把目錄換到 /
  • 重開 STDIN、STDOUT 和 STDERR 使它們不會與舊的 tty 連線。
  • 用下列方法把程式丟到後臺:

        fork && exit;
        

The Proc::Daemon module, available from CPAN, provides a function to perform these actions for you.

如何知道自己是否在互動地執行?

問得好。有的時候 "-t STDIN"N 和 "-t STDOUT" 可以提供線索,有時不行。

    if (-t STDIN && -t STDOUT) {
        print "Now what? ";
    }

在 POSIX 系統中,你可以用以下方法測試你自己的程式群組與現在控制你終端機 的是否相同:

    use POSIX qw/getpgrp tcgetpgrp/;
    open(TTY, "/dev/tty") or die $!;
    $tpgrp = tcgetpgrp(fileno(*TTY));
    $pgrp = getpgrp();
    if ($tpgrp == $pgrp) {
        print "foreground\n";
    } else {
        print "background\n";
    }

如何為緩慢的事件設定超時?

如同 Signals 和 Camel 書第六章裡所描述的,用 alarm() 函式, 或許再配合上一個訊號處理器。你也可以改用 CPAN 裡更具彈性的 Sys::AlarmCall 模組來做。

The alarm() function is not implemented on all versions of Windows. Check the documentation for your specific version of Perl.

如何設定 CPU 限額?

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

如何避免在 Unix 系統中產生殭屍程序?

使用 Signals 裡面叫 reaper 的程式碼,在接到 SIGCHLD 時會呼 叫wait(),或是用 perlfaq8 中的 "How do I start a process in the background?" 裡面寫的雙 fork 技巧。

如何使用 SQL 資料庫?

The DBI module provides an abstract interface to most database servers and types, including Oracle, DB2, Sybase, mysql, Postgresql, ODBC, and flat files. The DBI module accesses each database type through a database driver, or DBD. You can see a complete list of available drivers on CPAN: http://www.cpan.org/modules/by-module/DBD/ . You can read more about DBI on http://dbi.perl.org .

Other modules provide more specific access: Win32::ODBC, Alzabo, iodbc, and others found on CPAN Search: http://search.cpan.org .

如何使 system() 在收到 control-C 時退出?

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

    $rc = system($cmd);
    if ($rc & 127) { die "signal death" }

如何無阻塞地開啟一個檔案?

如果你有幸使用到支援無阻塞讀的系統(大部份 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: $!":

How do I install a module from CPAN?

最簡單的方法就是讓 CPAN 這個模組替你代勞。這個模組包含在 5.004及以後的版 本中。

    $ perl -MCPAN -e shell

    cpan shell -- CPAN exploration and modules installation (v1.59_54)
    ReadLine support enabled

    cpan> install Some::Module

如要手動安裝 CPAN 模組,或是任何按規矩發展的 CPAN模組,遵循以下步 驟:

1
把原始碼解壓到臨時目錄
2
    perl Makefile.PL
    
3
    make
    
4
    make test
    
5
    make install
    

如果你用的 perl 版本在編譯時沒有建入動態連結的功能,那你只消把第叄步 (make)換成 make perl 然後你就會得到一個新的 perl 執行檔,裡頭連 有你新加入的延伸。

在 ExtUtils::MakeMaker 裡面有更多關於建構模組的細節,並參考下一個問題,require 和 use 的區別是什麼?。

require 和 use 的區別是什麼?

Perl offers several different ways to include code from one file into another. Here are the deltas between the various inclusion constructs:

    1)  do $file is like eval `cat $file`, except the former
        1.1: searches @INC and updates %INC.
        1.2: bequeaths an *unrelated* lexical scope on the eval'ed code.

    2)  require $file is like do $file, except the former
        2.1: checks for redundant loading, skipping already loaded files.
        2.2: raises an exception on failure to find, compile, or execute $file.

    3)  require Module is like require "Module.pm", except the former
        3.1: translates each "::" into your system's directory separator.
        3.2: primes the parser to disambiguate class Module as an indirect object.

    4)  use Module is like require Module, except the former
        4.1: loads the module at compile time, not run-time.
        4.2: imports symbols and semantics from that package to the current one.

In general, you usually want "use" and a proper Perl module.

如何設定我自己的模組/庫路徑?

當你建構模組時,在產生 Makefiles 時使用 PREFIX 選項:

    perl Makefile.PL PREFIX=/mydir/perl LIB=/mydir/perl/lib

然後在執行用到此 模組/程式庫 的程式前先設好 PERL5LIB 環境變數(參考 perlrun ),或是用

    use lib '/mydir/perl/lib';

這樣與下面幾乎相同

    BEGIN {
        unshift(@INC, '/mydir/perl/lib');
    }

但 lib 模組檢測獨立於機器的子目錄。參見 Perl 的 lib 模組來獲取詳細資訊。

如何將我自己的程式的路徑加入到模組/庫搜尋路徑中?

    use FindBin;
    use lib "$FindBin::Bin";
    use your_own_modules;

如何在執行時將一個目錄加入到我的 include 路徑 (@INC) 中?

以下是我們建議更動引入路徑的方法:

    環境變數 PERLLIB 
    環境變數 PERL5LIB
    perl -Idir 命令列標誌
    use lib 編用,類似
        use lib "$ENV{HOME}/myown_perllib";

後者特別有用,因為它知道與機器相關的架構。lib.pm 機制模組是從 5.002 版開 始包含在 Perl 裡面的。

什麼是 socket.ph,從哪兒可以得到它?

It's a perl4-style file defining values for system networking constants. Sometimes it is built using h2ph when Perl is installed, but other times it is not. Modern programs "use Socket;" instead.

AUTHOR AND COPYRIGHT

Copyright (c) 1997-2003 Tom Christiansen and Nathan Torkington. All rights reserved.

This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.

Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.

譯者

陳彥銘,蕭百齡,兩隻老虎工作室

本頁面中文版由中文 man 手冊頁計劃提供。
中文 man 手冊頁計劃:https://github.com/man-pages-zh/manpages-zh

2003-11-25 perl v5.8.3