請閱讀特別針對你所使用的作業系統下的 perl 所寫的常見問題集和文件(例如, perlvms 、perlplan9,...),以取得 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
【譯註:中文版的 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 模組,這個模組較易使 用而且理論上也較據可攜性/移植性。
sysopen()
和 Fcntl 模組(標準 perl 的一部分)內 的
O_RDWR|O_NDELAY|O_NOCTTY
。在 sysopen 裡有對此方法更
詳盡的解說。
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 輸出/入 沒 用。在這種情況下,你得把你的行末字元寫死在程式碼內。
read()
或 sysread()
動作,則你需要安排一個鬧 鈴把手或提供一個逾時設定(參看 alarm)。如果你是用非阻擋式的 開檔,那麼就要配合非阻擋性的讀取,也就是說得用到4 個參數的 select()
來確 定此裝置的
輸出/入 是否已準備好了(參考
select
)。
說正經的,如果是碰到 Unix 密碼檔的話就不行 - Unix 密碼系統用的是單向的加 密函數。像 Crack 之類的程式可以暴力地(並聰明地)試著猜出密碼,但無法 (也不能)保證速戰速決。
如果你耽心的是使用者選取不良的密碼,你應該在使用者換密碼時主動審核(例如
說修改 passwd(1)
程式加入這個功能)。
system("cmd &")
或是用 fork,像 fork 裡寫的(在 perlipc 裡有更進一步的 範例)。如果你在 Unix 類的系統上的話,請注意以下幾件事情:
system("cmd&")
的話不會有這樣的問題。
$SIG{CHLD} = sub { wait };
在 Signals 有範例程式教你怎麼做。用 system("prog &")
的
話不會有僵屍程序的問題。
要小心的是,大多
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()
的逾時處理器的說明,或駱駝書第六
章。
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
.
總之,你可能做不到。但是如果你的 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()的效果。當程式或執行
緒(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
模組。
須注意儘管 SunOS 和 Solaris 在二進位執行檔上相容,這些值是相異的。自己去 想為什麼吧。
syscall(),那麼可以用
syscall 函數(說明在
perlfunc
裡)。
切記先查查看你的 perl 版本中所附的模組以及 CPAN 裡的模組,因為也許某人已 經寫了個這樣的模組。
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)。
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 解譯的情況發生。
$where = tell(LOG); seek(LOG, $where, 0);
seek()
檔案的另一部份然後再找回來。
seek()
檔案另一個相異的的部份,讀點東西,再回去找。
如果你所要做的只是假裝 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;
如要真的把看得見的指令列改掉,你可以設定 $0
這個變數值,如同 perlvar
裡寫的。但這方法並非各種作業系統都適用。像 sendmail之類的背景程式 (daemons)
就將它們的狀態放在那兒:
$0 = "orcus [accepting connections]";
eval()你
script 的輸出來裝出這種效果,在 comp.unix.questions
FAQ 裡有詳 細內容。
%ENV
的更改會持續到 Perl 離開,但是目錄更動則不會。
fork && exit;
-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"; }
alarm()
函數, 或許再配合上一個訊號處理器。你也可以改用
CPAN 裡更具彈性的 Sys::AlarmCall
模組來做。
system()
呼叫(參看 perlipc
裡的範例程式),然後設計一個訊號處理器,讓它把
INT 訊號傳給子程序。
sysopen():
use Fcntl; sysopen(FH, "/tmp/somefile", O_WRONLY|O_NDELAY|O_CREAT, 0644) or die "can't open /tmp/somefile: $!";
如果你用的 perl 版本在編譯時沒有建入動態連結的功能,那你只消把第三步 (make)換成 make perl 然後你就會得到一個新的 perl 執行檔,裡頭連 有你新加入的延伸。
在 MakeMaker裡面有更多關於建構模組的細節,並參考「如何保有 一份自己的 模組/程式庫目錄?」這個問題。
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() }
譯者:陳彥銘、蕭百齡
中譯版著作權所有:陳彥銘、蕭百齡及兩隻老虎工作室。本中譯版遵守並使用與原 文版相同的使用條款發行。