在Unix系統(tǒng)下用shell制作通用界面
目前在Unix系統(tǒng)下用shell編寫的菜單程序大都還是采用多級菜單的模式,這種模式的弊端在于菜單的層次多,界面本身不直觀,而且在編程過程中,將菜單的顯示格式和內(nèi)容以及所調(diào)用的子程序包括在菜單主程序中,使得程序只能滿足某個方面的需求,菜單程序本身不具備通用性。本程序設(shè)計采用了一種新的設(shè)計思路,將下拉菜單界面作為二維表格來處理,把下拉菜單的內(nèi)容以及所調(diào)用的子程序名稱分別存放在這兩個二維表中,通過對表的讀取,實現(xiàn)了控制光標移動、選擇菜單內(nèi)容以及調(diào)用子程序的目的。采用這種方式編寫出來的程序易于維護,通用性強。在程序本身不做任何改動的情況下,可以在同一操作平臺中進行任意移植,因而具有廣泛的應(yīng)用價值。這種思維模式并不局限在Unix系統(tǒng)下的shell編程,而且對于像C這樣的過程化語言也具有一定的借鑒意義。 設(shè)計思路 在下拉菜單制作過程中,整個下拉菜單界面所包含的菜單名稱以及所調(diào)用的子程序名之間的相互關(guān)系構(gòu)成了二維表,其中子菜單名稱和子程序名稱作為表的元素,通過選擇光標在表中上下左右移動,將表中元素讀出來,再進行處理運算,從而達到控制菜單的選擇以及子程序調(diào)用等目的。 <BR><BR> <TABLE cellSpacing=1 cellPadding=3 align=center border=0> <TBODY> <TR bgColor=#99ccff> <TD colSpan=5><FONT class=a14>表1 菜單項</FONT></TD></TR> <TR bgColor=#efefef> <TD>菜單1</TD> <TD>菜單2</TD> <TD>菜單3</TD> <TD>……</TD> <TD>菜單n</TD></TR> <TR bgColor=#efefef> <TD>菜單11</TD> <TD>菜單12</TD> <TD>菜單13</TD> <TD>……</TD> <TD>菜單1n</TD></TR> <TR bgColor=#efefef> <TD>菜單21</TD> <TD>菜單22</TD> <TD>菜單23</TD> <TD> </TD> <TD>菜單2n</TD></TR> <TR bgColor=#efefef> <TD>菜單31</TD> <TD>菜單32</TD> <TD>菜單33</TD> <TD> </TD> <TD>菜單3n</TD></TR> <TR bgColor=#efefef> <TD>……</TD> <TD> </TD> <TD> </TD> <TD> </TD> <TD> </TD></TR> <TR bgColor=#efefef> <TD>菜單m1</TD> <TD>菜單m2</TD> <TD>菜單m3</TD> <TD> </TD> <TD>菜單mn</TD></TR></TBODY></TABLE><BR><BR> <TABLE cellSpacing=1 cellPadding=3 align=center border=0> <TBODY> <TR bgColor=#99ccff> <TD colSpan=5><FONT class=a14>表2 對應(yīng)各菜單項的子程序</FONT></TD></TR> <TR bgColor=#efefef> <TD>子程序11</TD> <TD>子程序12</TD> <TD>子程序13</TD> <TD>……</TD> <TD>子程序1n</TD></TR> <TR bgColor=#efefef> <TD>子程序21</TD> <TD>子程序22</TD> <TD>子程序23</TD> <TD> </TD> <TD>子程序2n</TD></TR> <TR bgColor=#efefef> <TD>子程序31</TD> <TD>子程序32</TD> <TD>子程序33</TD> <TD> </TD> <TD>子程序3n</TD></TR> <TR bgColor=#efefef> <TD>……</TD> <TD> </TD> <TD> </TD> <TD> </TD> <TD> </TD></TR> <TR bgColor=#efefef> <TD>子程序m1</TD> <TD>子程序m2</TD> <TD>子程序m3</TD> <TD> </TD> <TD>子程序mn</TD></TR></TBODY></TABLE> 從上面的兩個表中不難看出除表1中的第一行為標題行(菜單欄),表1與表2有相同結(jié)構(gòu),兩個表之間的元素存在著一一對應(yīng)的關(guān)系,即每個菜單名稱下對應(yīng)著所調(diào)用的程序名(備注: 由于每個菜單標題欄下的子菜單的內(nèi)容是不一樣的,因而每個子菜單下的菜單數(shù)目也各不相同,表中一些元素可以是空值,它表示在此沒有菜單選擇項)。 文中介紹方法的技術(shù)難點在于選擇光標位置與實際光標位置的關(guān)系。所謂選擇光標位置是指在上下左右鍵的控制下,光標在菜單界面的位置,也就是光標在表中的行和列的位置。而實際光標位置是指光標在計算機屏幕上的實際位置。如何通過選擇光標位置計算出實際光標位置是本程序的一個難點。本程序的處理辦法是將選擇光標的行列位置分別作為計算函數(shù)的參數(shù),通過函數(shù)計算出實際光標的位置。 實現(xiàn)步驟 先將菜單的內(nèi)容按照一定的格式顯示在計算機屏幕上。顯示格式要依據(jù)表的結(jié)構(gòu)與內(nèi)容而定,而不能固定不變。如果事先固定下來,會使顯示格式與內(nèi)容之間產(chǎn)生矛盾,難以達到相互之間的統(tǒng)一,程序就不具備通用性。 選擇光標在菜單欄左右移動確定選擇項目的同時將菜單欄下所包含的子菜單內(nèi)容顯示出來。菜單欄最右端的菜單選擇項一般情況下表示“退出”,當選擇光標處于這個位置時,回車后退出整個菜單的選擇。 在菜單欄中回車或按↓鍵進入菜單欄下一級子菜單,按照所顯示的子菜單內(nèi)容,選擇光標上下移動確定所選定的子菜單內(nèi)容,回車執(zhí)行所調(diào)用的子程序,←、→兩個鍵退出子菜單的選擇。 需要說明的是由于在Unix系統(tǒng)中,光標在上下左右移動時, Unix系統(tǒng)的read命令無法捕獲←、↑、→、↓鍵的控制字符,無法對光標進行有效的控制,為了獲取移動光標的控制字符,這里需要用C語言編寫一個函數(shù),其主要功能是在光標進行上下左右移動時,能夠準確地返回←、↑、→、↓控制鍵的ASCII值,函數(shù)名為getchar。 程序分析 由于光標移動過程中涉及光標的行列位置等重復(fù)運算,運用函數(shù)可減少程序自身的長度,使程序變得短小、精悍。這里涉及以下一些函數(shù): 1. 畫框函數(shù) 前面提到顯示格式依據(jù)表的結(jié)構(gòu)而定,對菜單的邊框長度的設(shè)置不能固定不變,它要依據(jù)菜單標題欄的長度以及標題欄的標題個數(shù)而定。這個函數(shù)的功能就是依據(jù)菜單界面寬度畫邊框,參數(shù)$1表示邊框的橫線與豎線。 menu_x() { _R=$1 col_x=1 while [ col_x -le ${S_LENGTH} ] do if [ $_R ]; then echo $_R“c” else echo “c” fi col_x=‘expr $col_x + 2' done } 2. 計算實際光標在屏幕上的行列位置函數(shù) 選擇光標在標題欄左右移動的過程中,需要計算光標在屏幕上的實際位置,通過這個函數(shù)可以準確地計算出這個實際位置。其運算過程是將選擇光標在表中的行列位置作為函數(shù)的參數(shù),依據(jù)這兩個參數(shù)計算出光標在屏幕上的準確位置,并將選擇光標按照計算出的位置在屏幕上準確顯示。其中變量SCREEN- CUR表示表1的元素內(nèi)容,也就是菜單界面的菜單名稱,變量SCREEN-R和SCREEN-C分別表示實際光標在屏幕上位置。執(zhí)行的結(jié)果是將選擇光標的內(nèi)容按實際光標的位置顯示在屏幕上。 menu_c() { _C=$1 # 選擇光標在菜單界面的列位置 _R=$2 # 選擇光標在菜單界面的行位置 SCREEN_CUR=‘a(chǎn)wk -F“|” “NR==$_R { print }”menu|cut -d“|” -f$_C' if [ $_C -gt 1 ]; then F_C=‘expr $_C - 1' SCREEN_LENG=‘head -1 menu| cut -d“|” -f0-$F_C|sed -e ‘s/|//g' | awk ‘{ print length($0)}'' else SCREEN_LENG=0 fi SCREEN_R=‘expr $_R + 2' SCREEN_C=‘expr $C_COL + $SCREEN_LENG + 2' SCREEN_CUR_X=“33[${SCREEN_R};${SCREEN_C}H${SCREEN_CUR}” } 3. 計算選擇光標在移動過程中位置的函數(shù) 選擇光標在上下左右的移動過程中,其在菜單界面的位置也隨之發(fā)生變化,需要通過運算,以確定選擇光標在菜單界面的準確位置。其中參數(shù)$1表示上下左右鍵所返回的ASCII值,當參數(shù)$1等于2或3時,表示選擇光標在上移或左移; 等于1或4時表示選擇光標在向下移動或向右移動。參數(shù)$2表示選擇光標移動過程中在表1中的位置,參數(shù)$3表示選擇光標移動過程中所限定的區(qū)間范圍。 menu_x_y() { _Z=$1 _S=$2 _L=$3 case $_Z in 2|3) if [ $_S -gt 1 ] then _S=‘expr $_S - 1' else _S=$_L fi 1|4) if [ $_S -lt $_L ] then _S=‘expr $_S + 1' else _S=1 fiesac return $_S } 4. 計算菜單界面每個菜單欄下的菜單數(shù)目函數(shù) 通常情況下每個菜單標題下所包含的內(nèi)容是不一樣的,因而每個菜單欄下菜單的數(shù)目也是不相同的,需要對每個菜單欄下的菜單數(shù)目進行計算,參數(shù)$1表示選擇光標在菜單欄下的列位置。 menu_row_number() { _H=$1 S_NUMBER=‘cut -d“|” -f$_H menu|sed -e ‘s/ //g'-e ‘/^$/d'| awk ‘END { print NR}'' } 5. 執(zhí)行子程序函數(shù) 子程序名存在prg文件中,表2中的元素就是子程序名。調(diào)用子程序的過程實際就是根據(jù)選擇光標在菜單界面的行列位置將相應(yīng)位置的元素讀出來,然后依據(jù)表2所提供的程序名判斷是否真實存在,如果存在則執(zhí)行。 menu_prg() { _C=$1 # 選擇光標在菜單界面的列位置 _R=$2 # 選擇光標在菜單界面的行位置 prg_name=‘a(chǎn)wk -F“|” “NR==$_R { print }” prg|cut -d“|” -f$_C' if [ -s $prg_name ] then eval $prg_name # 執(zhí)行所調(diào)用的子程序 else echo “07” fi } 下面是主程序: # 設(shè)置菜單界面前景與背景顏色 COLOR1=“33[32;44;1m” # 菜單界面的前景色 COLOR2=“33[33;45;1m” # 菜單界面的背景色 COLOR3=“33[37;40;1m” # 選擇光標的顏色 # 對程序中所用的一些變量進行初始化設(shè)置 CUR_R=1 #選擇光標在菜單界面的行位置 CUR_C=1 #選擇光標在菜單界面的列位置 S_LENGTH=‘head -1 menu|sed -e ‘s/|//g' | awk ‘{ print length($0)}'' # 確定菜單界面的寬度 S_MENU=‘head -1 menu| awk -F“|” ‘{ print NF}'' # 確定菜單標題欄的字段數(shù) C_COL=‘expr ( 80 - $S_LENGTH - 4 ) / 2 ' # 確定菜單界面的起始位置 echo ${COLOR1}; clear # 按格式顯示菜單界面 row=2 # 顯示行 [2-23] while [ row -le 23 ] do case $row in 2) echo “33[${row};${C_COL}H┏c”; menu_x “━”; echo “┓” 3) echo “33[${row};${C_COL}H┃c”; head -1 menu |sed -e ‘s/|//g' |awk ‘{ print $0 “┃” }'23) echo “33[${row};${C_COL}H┗c”; menu_x “━”; echo “┛c” *) echo “33[${row};${C_COL}H┃c”; menu_x “ ”; echo “┃” esac row=‘expr $row + 1' done while true do menu_c $CUR_C $CUR_R # 計算選擇光標的位置 echo “${COLOR2}${SCREEN_CUR_X}c” stty -echo getchar # 等待選擇 ANS_X=$? # 返回ASCII值 stty echo echo “${COLOR1}${SCREEN_CUR_X}c” case $ANS_X in 3|4) menu_x_y $ANS_X $CUR_C $S_MENU #選擇光標在菜單標題欄中左右移動 CUR_C=$?1|10) if [ $CUR_C = $S_MENU ] #按回車鍵或↓鍵進入子菜單 then setcolor -n ; clear; break fi menu_row_number $CUR_C # 在菜單標題欄下將所包含子菜單內(nèi)容顯示在屏幕上 row=2 while [ row -le ${S_NUMBER} ] do menu_c $CUR_C $row echo “${COLOR3}${SCREEN_CUR_X}c” row=‘expr $row + 1 ' done while true do menu_c $CUR_C $CUR_R echo “${COLOR2}${SCREEN_CUR_X}c” stty -echo getchar ANS_Y=$? stty echo echo “${COLOR3}${SCREEN_CUR_X}c” case $ANS_Y in 1|2) menu_x_y $ANS_Y $CUR_R $S_NUMBER #上下移動選擇光標 CUR_R=$?3|4) menu_x_y $ANS_Y $CUR_C $S_MENU #左右移動選擇光標退出子菜單選擇 CUR_C=$? CUR_R=1 break10) menu_prg $CUR_C $CUR_R#回車后執(zhí)行子程序 *) echo “07”esac done *) echo “07c”esac done 小結(jié) 本文所論述的是如何在Unix系統(tǒng)下利用shell制作通用的下拉菜單。這種通用性集中體現(xiàn)在實現(xiàn)了菜單下的菜單名稱以及所調(diào)用的子程序名稱與菜單主程序的分離,菜單界面下子菜單名稱以及所調(diào)用的子程序名稱分別存放在兩個文本文件中,主程序通過對這兩個文件的讀取實現(xiàn)了菜單程序的正確顯示與選擇功能。只要對這兩個文本文件進行編輯,不需要對主程序進行任何改動,即可完成Unix系統(tǒng)下拉菜單的制作,使得菜單制作非常快捷、靈活。同時可以很方便地進行移植,因而有較強的通用性。而且采用這種方式制作出來的下拉菜單界面比較直觀、明了,操作起來更加簡單、方便。 備注:在編輯menu和prg文件時,子菜單名稱和子程序名稱是一一對應(yīng)的關(guān)系,所以子菜單與子程序在文件中位置要擺放正確,不能亂放。由于在本程序中awk語句的所有分隔符都是“|”,而不是空格,因而文本文件中的分隔符也是“|”,而不能用空格,這一點在編輯這兩個文件時要特別注意。
