目前在Unix系統下用shell編寫的菜單程序大都還是采用多級菜單的模式,這種模式的弊端在於菜單的層次多,界面本身不直觀,而且在編程過程中,將菜單的顯示格式和內容以及所調用的子程序包括在菜單主程序中,使得程序只能滿足某個方面的需求,菜單程序本身不具備通用性。本程序設計采用了一種新的設計思路,將下拉菜單界面作為二維表格來處理,把下拉菜單的內容以及所調用的子程序名稱分別存放在這兩個二維表中,通過對表的讀取,實現了控制光標移動、選擇菜單內容以及調用子程序的目的。采用這種方式編寫出來的程序易於維護,通用性強。在程序本身不做任何改動的情況下,可以在同一操作平台中進行任意移植,因而具有廣泛的應用價值。這種思維模式並不局限在Unix系統下的shell編程,而且對於像C這樣的過程化語言也具有一定的借鑒意義。
設計思路
在下拉菜單制作過程中,整個下拉菜單界面所包含的菜單名稱以及所調用的子程序名之間的相互關系構成了二維表,其中子菜單名稱和子程序名稱作為表的元素,通過選擇光標在表中上下左右移動,將表中元素讀出來,再進行處理運算,從而達到控制菜單的選擇以及子程序調用等目的。
表1 菜單項
菜單1
菜單2
菜單3
……
菜單n
菜單11
菜單12
菜單13
……
菜單1n
菜單21
菜單22
菜單23
菜單2n
菜單31
菜單32
菜單33
菜單3n
……
菜單m1
菜單m2
菜單m3
菜單mn
表2 對應各菜單項的子程序
子程序11
子程序12
子程序13
……
子程序1n
子程序21
子程序22
子程序23
子程序2n
子程序31
子程序32
子程序33
子程序3n
……
子程序m1
子程序m2
子程序m3
子程序mn
從上面的兩個表中不難看出除表1中的第一行為標題行(菜單欄),表1與表2有相同結構,兩個表之間的元素存在著一一對應的關系,即每個菜單名稱下對應著所調用的程序名(備注: 由於每個菜單標題欄下的子菜單的內容是不一樣的,因而每個子菜單下的菜單數目也各不相同,表中一些元素可以是空值,它表示在此沒有菜單選擇項)。
文中介紹方法的技術難點在於選擇光標位置與實際光標位置的關系。所謂選擇光標位置是指在上下左右鍵的控制下,光標在菜單界面的位置,也就是光標在表中的行和列的位置。而實際光標位置是指光標在計算機屏幕上的實際位置。如何通過選擇光標位置計算出實際光標位置是本程序的一個難點。本程序的處理辦法是將選擇光標的行列位置分別作為計算函數的參數,通過函數計算出實際光標的位置。
實現步驟
先將菜單的內容按照一定的格式顯示在計算機屏幕上。顯示格式要依據表的結構與內容而定,而不能固定不變。如果事先固定下來,會使顯示格式與內容之間產生矛盾,難以達到相互之間的統一,程序就不具備通用性。
選擇光標在菜單欄左右移動確定選擇項目的同時將菜單欄下所包含的子菜單內容顯示出來。菜單欄最右端的菜單選擇項一般情況下表示“退出”,當選擇光標處於這個位置時,回車後退出整個菜單的選擇。
在菜單欄中回車或按↓鍵進入菜單欄下一級子菜單,按照所顯示的子菜單內容,選擇光標上下移動確定所選定的子菜單內容,回車執行所調用的子程序,←、→兩個鍵退出子菜單的選擇。
需要說明的是由於在Unix系統中,光標在上下左右移動時, Unix系統的read命令無法捕獲←、↑、→、↓鍵的控制字符,無法對光標進行有效的控制,為了獲取移動光標的控制字符,這裡需要用C語言編寫一個函數,其主要功能是在光標進行上下左右移動時,能夠准確地返回←、↑、→、↓控制鍵的ASCII值,函數名為getchar。
程序分析
由於光標移動過程中涉及光標的行列位置等重復運算,運用函數可減少程序自身的長度,使程序變得短小、精悍。這裡涉及以下一些函數:
1. 畫框函數
前面提到顯示格式依據表的結構而定,對菜單的邊框長度的設置不能固定不變,它要依據菜單標題欄的長度以及標題欄的標題個數而定。這個函數的功能就是依據菜單界面寬度畫邊框,參數$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. 計算實際光標在屏幕上的行列位置函數
選擇光標在標題欄左右移動的過程中,需要計算光標在屏幕上的實際位置,通過這個函數可以准確地計算出這個實際位置。其運算過程是將選擇光標在表中的行列位置作為函數的參數,依據這兩個參數計算出光標在屏幕上的准確位置,並將選擇光標按照計算出的位置在屏幕上准確顯示。其中變量SCREEN- CUR表示表1的元素內容,也就是菜單界面的菜單名稱,變量SCREEN-R和SCREEN-C分別表示實際光標在屏幕上位置。執行的結果是將選擇光標的內容按實際光標的位置顯示在屏幕上。
menu_c()
{ _C=$1 # 選擇光標在菜單界面的列位置
_R=$2 # 選擇光標在菜單界面的行位置
SCREEN_CUR=‘awk -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. 計算選擇光標在移動過程中位置的函數
選擇光標在上下左右的移動過程中,其在菜單界面的位置也隨之發生變化,需要通過運算,以確定選擇光標在菜單界面的准確位置。其中參數$1表示上下左右鍵所返回的ASCII值,當參數$1等於2或3時,表示選擇光標在上移或左移; 等於1或4時表示選擇光標在向下移動或向右移動。參數$2表示選擇光標移動過程中在表1中的位置,參數$3表示選擇光標移動過程中所限定的區間范圍。
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
fi;;
esac
return $_S
}
4. 計算菜單界面每個菜單欄下的菜單數目函數
通常情況下每個菜單標題下所包含的內容是不一樣的,因而每個菜單欄下菜單的數目也是不相同的,需要對每個菜單欄下的菜單數目進行計算,參數$1表示選擇光標在菜單欄下的列位置。
menu_row_number()
{ _H=$1
S_NUMBER=‘cut -d“|” -f$_H menu|sed -e ‘s/ //g'-e ‘/^$/d'| awk ‘END { print NR}''
}
5. 執行子程序函數
子程序名存在prg文件中,表2中的元素就是子程序名。調用子程序的過程實際就是根據選擇光標在菜單界面的行列位置將相應位置的元素讀出來,然後依據表2所提供的程序名判斷是否真實存在,如果存在則執行。
menu_prg()
{ _C=$1 # 選擇光標在菜單界面的列位置
_R=$2 # 選擇光標在菜單界面的行位置
prg_name=‘awk -F“|” “NR==$_R { print }” prg|cut -d“|” -f$_C'
if [ -s $prg_name ]
then
eval $prg_name
# 執行所調用的子程序
else
echo “07”
fi
}
下面是主程序:
# 設置菜單界面前景與背景顏色
COLOR1=“33[32;44;1m” # 菜單界面的前景色
COLOR2=“33[33;45;1m” # 菜單界面的背景色
COLOR3=“33[37;40;1m” # 選擇光標的顏色
# 對程序中所用的一些變量進行初始化設置
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}''
# 確定菜單標題欄的字段數
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
# 在菜單標題欄下將所包含子菜單內容顯示在屏幕上
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
break;;
10) menu_prg $CUR_C $CUR_R;; #回車後執行子程序
*) echo “07”;;
esac
done
;;
*) echo “07c”;;
esac
done
小結
本文所論述的是如何在Unix系統下利用shell制作通用的下拉菜單。這種通用性集中體現在實現了菜單下的菜單名稱以及所調用的子程序名稱與菜單主程序的分離,菜單界面下子菜單名稱以及所調用的子程序名稱分別存放在兩個文本文件中,主程序通過對這兩個文件的讀取實現了菜單程序的正確顯示與選擇功能。只要對這兩個文本文件進行編輯,不需要對主程序進行任何改動,即可完成Unix系統下拉菜單的制作,使得菜單制作非常快捷、靈活。同時可以很方便地進行移植,因而有較強的通用性。而且采用這種方式制作出來的下拉菜單界面比較直觀、明了,操作起來更加簡單、方便。
備注:在編輯menu和prg文件時,子菜單名稱和子程序名稱是一一對應的關系,所以子菜單與子程序在文件中位置要擺放正確,不能亂放。由於在本程序中awk語句的所有分隔符都是“|”,而不是空格,因而文本文件中的分隔符也是“|”,而不能用空格,這一點在編輯這兩個文件時要特別注意。