Thursday, October 28, 2004
Anti-idle by Screen and Expect
目標
使用 Screen + Expect,取代 Windows 上一般的 Enhanced Telnet Client 如 PacketSiTE 之流,達到在 server 上真正「一直掛站」的效果。
Client 端不限,PuTTY 是不錯的選擇。
Server 端需要 screen、expect,以及可設定 prompt 的 shell,下面只介紹 tcsh 的作法。
首先,在 screenrc 裡面加上這一行:
hardstatus alwayslastline '%{.bY}[%H]%{.bR} %?%-LW %?%{..W}%n%f %t%{.bR}%?%+LW%?%=%{.bY}%0c'這 是用來在 terminal 上,使用最底下一行,顯示 screen 的 status line,會顯示的東西,除了 screen 裡各個「window」的 number、flag 及 title等資訊。額外左端加上 server 的 hostname,右端加上 server 的時間。這樣子長的就會有點像 Windows 的工作列,右端擺上 server 時間還有個好處是,由於每分鐘會變動一次,遇到那種 idle 太久會斷線的網路環境裡,從 local 到 server 端的連線就不會斷線,因為每分鐘至少會傳一次資料。
以下統一一下 terminology 好了:使用 PuTTY 這端叫 local 端,跑 screen + expect 的 server 那端叫 server 端,然後連到的 bbs 站叫 remote 端。
另 外要注意的是,目前的 window 會用高亮度顯示,上一個 window,在數字右邊會有一個 - 號。背景 window 有「嗶」聲發生,但我們還沒「轉台」過去看的 window,數字旁邊會有一個 ! 號。這個 ! 號,有助於我們掌握是哪一個 window 的 BBS 有訊息傳來。另外,您最好額外在 screenrc 裡設定:
bell_msg 'Bell in window %n^G'
這 樣 screen 還會在背景 window 有「嗶」發聲時,顯示短暫兩、三秒的訊息,告知是那一個窗有「嗶」聲。這個設定裡頭的 ^G 表示,然後要真的發出「嗶」聲。因為 screen 預設背景 window 的「嗶」是不發出聲音的,故利用 bell_msg 我們可以迫使其發出聲音來。
另外要注意的是,我的 screen 版本為:
SHEEL> screen -v Screen version 3.09.11 (FAU) 14-Feb-02 SHEEL> uname -sr FreeBSD 4.10-PRERELEASE
我 在另一台 Linux(RedHat)上設 hardstatus,底下會顯示亂碼一行。該機器的 screen 版本比我稍舊,詳細版本忘記了。目前還不確定是該版本的 screen 有問題,亦或是 screenrc 編寫的有問題(RedHat 預設了一個複雜的 screenrc)。
由於 hardstatus line 會佔去一行的空間,而一般的 BBS 都至少要是 80 x 24,故原始的 PuTTY 設定便無法容納這額外需要的一行。所以請在 PuTTY 的 Window 設定頁裡,將 Rows 改成 25,以容納這額外多出來的 hardstatus line。
接著,利用 screen 的 dynamic title 機制,送出 title-escape-sequence 以根據 remote bbs 名稱修改 screen window title。為了要在 remote bbs 斷線之後,能夠把 screen window title 修改回來,所以一併參照 screen 的文件,替 shell 也應用上這個 dynamic title 機制。
Screen 的 dynamic title 的原理為:當畫面顯示了一個 title-escape-sequence (
k ) 時,就會於這個 sequence 後面,尋找某個指定的字串。從這個字串的後面一個字元開始,到第一個空白字元出現為止,這中間的字串,就是會被設定為 screen window title 的字串。整個動作,會發生在 title-escape-sequence 後的第一個 \n 出現時。而要讓 screen 照上述這麼做,我們必須要指定這一個「某個指定的字串」,以及若該 screen window title 的字串不存在(指定字串之後馬上碰上 \n) 時,所要顯示的字串。設定方法便是,在 screenrc 裡,設定 shelltitle 為 'SEARCH|NAME',其中,SEARCH 就是那個「某個指定的字串」,而 NAME 就是若該字串不存在時,所要顯示的東西。配合 prompt 這個 shell 指令,反映到 screen window title 上來。tcsh + screen 的設定方法如下: 在 tcshrc 裡設定:
set prompt = "%{\ek\e\\%}%% "我自己的 prompt 會複雜一點,但總之,在最後面加上上面這個字串就對了。然後在 screenrc 裡設定:
shelltitle '% |TCSH'
這樣子,,screen 就會抓取每個 shell 指令,反映到 screen window title來。試打個 top 指令,就可以從剛剛在第 3 步裡所設定的 hardstatus line 上面,體驗到 dynamic title 的效果了。
接著,我們要編寫 except 檔,來達到「上 BBS 不會斷線」,以及「依據所上的 BBS 站,修改 screen window title」的功能。Expect 程式如下:
#!/usr/local/bin/expect -f # # Name: bbs_noidle.expect # Description: expect script to run a shell which avoids being # "autologged out" by sending a
every # specified timeout limit if no other I/O has occurred. # Author: Don Libes, December 30, 1991 # Modified by: Clive Lin to fit tw BBS. # bbs_noidle.expect telnet bbs.some.where # And send only # Modified by: Jeff Hung @ 20040829 to add # these features: # - Use zh-telnet to connect to BBS. # - Able to use abbrevation instead of complete hostname. # - Use screen's 'title-escape-sequence' to dynamically # set window title. # set timeout 180 set anti_idle "\177"; log_user 0 system stty -echo raw if {[llength $argv] <= 0} { send "Usage: bbs_noidle.expect ( | )\n"; exit 0; } set bbs_abbr "$argv"; set bbs_host "$argv"; switch $bbs_abbr { lady { set bbs_host "bbs.lady.idv.tw" } tkucs { set bbs_host "bbs.cs.tku.edu.tw" } nctuiim { set bbs_host "bbs.iim.nctu.edu.tw" } tku { set bbs_host "bbs.tku.edu.tw" } ptt { set bbs_host "ptt.cc" } zoo { set bbs_host "zoo.twbbs.org" } } # Cause screen to print where we're connecting to. send "We're connecting to \033k\033\\% $bbs_abbr %\n\r"; eval spawn -noecho /usr/local/bin/zh-telnet $bbs_host expect { timeout { send $anti_idle; exp_continue } -re .+ { send_user -raw -- $expect_out(buffer); exp_continue } -i $user_spawn_id -re .+ { send -- $expect_out(buffer); exp_continue } } 傳 給這個 expect 程式的命令列參數,可以是在程式中的 switch {} 裡頭所列的 host name abbrevation,或是正確完整可傳給 telnet 當作命令列參數的的 host name。修個這裡的列表,您就可以編寫您自己的 bbs list。
其中的秘訣在於這一行:
send "We're connecting to \033k\033\\% $bbs_abbr %\n\r";
在 連線前,先送出 title-escape-sequence 修改 screen window title,達到顯示目前所上的 BBS 站的效果。在程式最開頭的 set timeout 可以設定「防呆」的時間為多少秒。 而 set anti_idle 則可以設定,防呆時要送出那個字串。一般來說,送出 \177,也就是 DELETE 字元,差不多就很夠用了。
補充一下,在 .cshrc 裡頭加上:
if ( ! $?WINDOW ) then alias screen 'env LC_CTYPE=en_US.ISO8859-1 screen -x -RR' else alias screen 'env LC_CTYPE=en_US.ISO8859-1 screen' endif |
然後在 .login 裡加上:
if ( ! $?WINDOW ) then screen endif |
就可以在 login 後自動進入原先的 screen,進入仍在掛站中的 BBS。當然,這樣子離線就不能真的打 exit 離線,要用 screen 的 detach 離線法:C-a D D。
若 是有要在 shell 裡工作的話,請建立一個同 UID 的帳號,好比叫 jeffbbs,在 PuTTY 裡建立不同名稱的連線,預設使用 jeffbbs 登入,這樣子的話,就可以在用 jeffbbs 登入時,享用 Screen + Expect 掛站大法。而在使用 jeffhung 登入時,則是進入正常的 shell。這是因為 screen 是用 login id 來判定 session 歸屬的。
Simple DB3 C++ Wrapper
為了寫程式方便順手包的 db3++.h,將操作 Berkeley DB ver.3[1]的 C API 稍作包裝,以便在 C++ 裡更好用。因為純粹以快速寫完為原則,所以一些成員函式的 prototype 設計就沒有仔細構思,error handling 也未盡完善,不過以下所展示的已足以說明如何包裝、使用 db3:
#include |
Wednesday, October 20, 2004
[Perl] Escape Full Big5 Character for R.E. in Perl under 5.8
由於 perl 5.8 以下(不包括 5.8),其字串是 byte 的組成單位是 byte 而非 character,所以碰上內含有 regular expression 特殊字元的 big5 字,就會造成 regular expression 判斷出錯。例如:
print (($s =~ m/會議/) ? 'match' : 'no match'); |
不管 $s 是否含有「會議」一詞,皆會印出 'match'。這是因為「會」字的第二個 byte,是個 '|',而 '|' 在 perl regular expression 裡,代表著特殊的意義。這個問題的通常解法,是將含有 regular expression 特殊字元的全形 Big5 字元,改寫成以 hex 數字表示的形式。例如,m/會議/ 就可以被改寫成 m/[\xB7][\x7C]議/。不過,如果是程式裡面的 regular expression 那還好,見一個改一個便是。可是,若要 match 的字串是 run-time 才能知道的,好比說 user input 的話,這招就沒效了。
這問題的終極且最好的解法,當然是換用 perl 5.8。然而,換用 perl 5.8,可能代表著整套程式都必須跟著升級,而這通常會是很麻煩的一件事,而且,也不一定每一台機器都有灌 perl 5.8。因此,我們需要另一個方法來迴避這個問題。
要迴避這個問題,其實策略也很簡單:「(只)檢查每一個全形 big5 字元,若其內含有 r.e. 特殊字元,便將之代換成以 hex 數字表示的形式」。因此,我們有了以下的 function:
my $__FULLBIG5CHARS = q{
# Table adopted from http://freebsd.sinica.edu.tw/zh-tut/big5.html
# (http://netlab.cse.yzu.edu.tw/~statue/freebsd/zh-tut/big5.html)
#
# 第一位元組 第二位元組 字區 制訂
# ----------------------------------------------------------------------------------------------------
([\xA1-\xA2][\x40-\x7E\xA1-\xFE])| # 各種符號區 Big5_1984
( [\xA3][\x40-\x7E\xA1-\xBF])| # 各種符號區 (?]括標點符號、ASCII 全形符號、注音符號等) Big5_1984
( [\xA3] [\xE1])| # 歐元符號 CP950
([\xA4-\xC5][\x40-\x7E\xA1-\xFE])| # 常用字區 Big5_1984
( [\xC6] [\x40-\x7E])| # 常用字區 Big5_1984
( [\xC6] [\xA1-\xFE])| # 罕用符號區 Big5_ETen
( [\xC7][\x40-\x7E\xA1-\xFE])| # 罕用符號區 (?]括日文、俄文等) Big5_ETen
( [\xC8][\x40-\x7E\xA1-\xD3])| # 罕用符號區 (?]括俄文、輸入法特殊符號等) Big5_ETen
([\xC9-\xF8][\x40-\x7E\xA1-\xFE])| # 次常用字區 Big5_1984
( [\xF9][\x40-\x7E\xA1-\xD5])| # 次常用字區 Big5_1984
( [\xF9] [\xD6-\xDC])| # 七個擴充字 (碁, 銹, 裏, 墻, 恒, 粧, 嫺) Big5_ETen
( [\xF9] [\xDD-\xFE])| # 表格符號區 Big5_ETen
};
sub mmi_escape_big5_re
{
my ($mbcs) = @_;
die if (!defined($mbcs));
# One character per list element.
my @chars = ();
while ($mbcs =~ /\G($__BIG5CHARS)/gox) {
push @chars, $&;
}
my $str = '';
for my $char (@chars) {
if (length($char) > 1) {
if ($char =~ m/[\[|\^\$]/o) {
my $c0 = sprintf('[\x%X]', ord(substr($char, 0, 1)));
my $c1 = sprintf('[\x%X]', ord(substr($char, 1, 1)));
$char = "$c0$c1";
}
else {
}
}
$str .= $char;
}
return $str;
} |
使用這個 function 很簡單,在用之前,把要用來 match 的變數,用這個 function 先「過濾」一遍就可以了。例如:
$content = 'blah~ blah~ blah~';
while ($input = <>) {
chomp($input);
$re = mmi_escape_big5_re($input);
if ($content =~ m/$re/) {
print "There are '$input' in \$content.\n";
}
} |
Subscribe to Comments [Atom]
