$^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 有關的,那就 看另一份常見問題集吧。)
【譯注:中文版的 PerlCGI 程式設計常見問題集可以在下列網址中找到: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 目錄下的檔案來擷取; 而在其他系統上,設備的名稱無疑地會不一樣。以下是一些 在設備互動時可能遭遇的共同問題:
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 &")
的 話不會有僵 程序的問題。
你并不能真的 ``捕捉'' 一個控制字元。而是控制字元產生一個訊號讓你捕捉。關於 訊號的資料可以在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()
檔案另一個相異的的部份,讀點東西,再回去找。
學習 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 標準配備之一),但一直沒 真正完工?,F在,你的最佳選擇就是從CPAN 來的 Comm.pl 程式庫。
首先要注意的是,如果你的目的是為了安全(例如避免人們偷看到密碼),那你應 該重寫你的程式,把重要的資訊從參數中剔除。光是隱藏起來不會讓你的程式變得 完全安全。
如要真的把看得見的指令列改掉,你可以設定$0
這個變數值,如同perlvar 里寫的。但這方法并非各種作業系統都適用。像 sendmail之類的背景程式 (daemons) 就將它們的狀態放在那兒:
$0 = "orcus [aclearcase/" target="_blank" >ccepting connections]";
eval()你
script 的輸出來裝出這種效果,在 comp.unix.questionsFAQ 里有詳 細內容。
%ENV
的更改會持續到 Perl 離開,但是目錄更動則不會。 假設你的系統支援這種功能,那就只要送個適當的訊號給此程序(參看 kill)。通常是先送一個 TERM 訊號,等一下下,然後再送個KILL 訊號去終結它。
如果你所指的是離線的程序(未與 tty 連線者),那下列的程序據說在大部份的 Unix系統都能用。非 Unix 系統的使用者應該檢查 Your_OS::Process 模組看看有 沒有其他的解決方案。
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() }