【例6-1】使用#!為腳本文件自設定解釋程序。
$ cat lsdir
#!/bin/od -c
if [ $# = 0 ]
then
dir=.
else
dir=$1
fi
find $dir -type d -print
$ chmod u+x lsdir
$ ./lsdir
0000000 # ! / b i n / o d - c \n i f
0000020 [ $ # = 0 ] \n t h e n
0000040 \n d i r = . \n e l s e \n
0000060 d i r = $ 1 \n f i \n f i n d $
0000100 d i r - t y p e d - p r i
0000120 n t \n \n
腳本文件中第一行如果缺少了#!,那麼,系統就會用默認的shell程序來解釋執行腳本文件的文本。一般系統的默認shell是/bin/sh,LINUX的默認shell是/bin/bash。
有三種方法可以執行腳本文件。
(1) sh < lsdir
(2) sh lsdir
sh lsdir /bin
(3) chmod u+x lsdir 給文件lsdir可執行屬性
./lsdir
【例6-2】標准輸入重定向的使用舉例。
./myap < try.in
如果你在編寫一個程序btree.c完成一個數據結構的作業,根據一棵二叉樹的先序,中序,求後序。程序運行的時候需要輸入先序序列,中序序列,然後打印出後序序列。由於程序邏輯較復雜,先序序列和後序序列分別為下列順序時,程序運行不正確。
ABFGEHILMOPCDJKNQR
FGBHELIOPMACJDQNRK
這樣,就需要反復地修改程序文件btree.c,編譯後運行./btree,然後根據程序提示,從鍵盤輸入上述的兩個字符串數據。反復的調試需要反復的輸入上述數據,這種重復性的勞動效率很低,而且容易出錯。這樣,就可以將上述兩行文本編輯成文件btree.tst1, 利用shell的輸入重定向,運行./btree < btree.tst1。
【例6-3】簡單的Here document。
cat << TEXT
**********
* Hello! *
**********
TEXT
上述命令執行結果,在屏幕上顯示:
**********
* Hello! *
**********
這裡,<<符號後面是定界符,在shell輸入中下一個TEXT之前的這段文本,算作cat命令的標准輸入。
【例6-4】在Here document中進行命令替換和變量替換。
cat << TOAST
Hello! Time: `date`
My Home Directory is $HOME
Bye!
TOAST
上述命令執行結果為:
Hello! Time: Sat Jul 27 14:47:56 BEIJING 2004
My Home Directory is /usr/jiang
Bye!
【例6-5】在Here document中禁止命令替換和變量替換。
cat << \TOAST
Hello! Time: `date`
My Home Directory is $HOME
Bye!
TOAST
cat << 'TOAST'
Hello! Time: `date`
My Home Directory is $HOME
Bye!
TOAST
上述兩種情況,輸出結果都是:
Hello! Time: `date`
My Home Directory is $HOME
Bye!
【例6-6】B-shell的標准錯誤輸出重定向舉例。
(1) cc myap.c -o myap 2> myap.err
將cc命令的stderr重定向到文件myap.err中。
(2) 設try是程序員設計的某個應用程序。
try > try.out 2>try.err
try 1> try.out 2>try.err
將try程序執行後的stdout或stderr分別定向到兩個不同的文件中(其中的1和2,是文件描述符,分別是stdout和stderr,如果換成其它數字也可以對try的其它文件描述符輸出改向)。
【例6-7】B-shell指定文件描述符的輸出重定向。
myap >rpt 2>&1
或者:
myap 1>rpt 2>&1
【例6-8】B-shell的管道處理舉例。
(1) ls -l | grep ’^d’
前一命令的stdout作後一命令的stdin。
(2) cc myap.c -o myap 2>&1 | more
前一命令的stdout+stderr作為下一命令的stdin。這裡管道操作的優先級高,先完成cc的標准輸出管道到下個命令的標准輸入,然後,將文件描述符重定向的和文件描述符1一樣,就完成了將標准輸出和標准錯誤輸出都管道到下個命令的目的。
【例6-9】shell變量的定義和引用舉例。
定義一個名為addr的變量,存放一個IP地址字符串。
$ addr=20.1.1.254 (最左側的$為sh命令提示符)
$ echo $addr (引用變量addr,echo執行前,sh完成變量替換)
20.1.1.254
$ city="Beijing, China"
$ echo $city
Beijing, China
$ echo Connected to $proto Network
Connected to Network
$ proto=TCP/IP
$ echo Connected to $proto Network
Connected to TCP/IP Network
【例6-50】使用shell函數的例子。
這個例子是一個廣域網通信適配卡驅動程序安裝腳本的一部分。
這個通信適配卡安裝之前要求輸入硬件的中斷號,I/O基地址和通信速率。然後,列出操作員的輸入,等待確認後才執行安裝操作。
安裝操作在41~85行之間,被省略。
第4~16行的函數get_answer打印出一條消息,然後,強制輸入y或者n,否則繼續要求用戶輸入。函數體內使用了$1引用調用函數get_answer的命令行參數,用return使得shell函數象普通命令一樣有返回碼,供條件判斷使用。
第20~34行的函數get_val有四個參數。第一個參數$1存放用戶輸入值的shell變量的名字,第二個參數$2是輸入之前給用戶的提示信息,第三個參數$3是用戶直接按下回車時的缺省值,最後一個參數$4是允許取值的有效值列表。函數get_val強制用戶輸入一個有效值列表中的有效值,否則,就給出有效值列表做提示,並進一步要求用戶重新輸入。函數體內使用了while循環和for循環結構。第29行的break命令含有參數2,可以跳出兩層循環。
腳本程序執行時從第36行開始執行。由於腳本文件前面有了函數說明,shell記下了函數名字get_val和get_answer作為內部命令,在執行命令get_val和get_answer時,shell都轉去執行函數體,而不是到磁盤上尋找這樣名字的命令文件。
$ awk '{print NR,$0}' WanCom
1 # Shell function to read a Y/N response
2 # Usage: get_answer <message>
3 #
4 get_answer()
5 {
6 while true
7 do
8 echo "$1? (y/n) \c"
9 read yn
10 case $yn in
11 [yY]) return 0;;
12 [nN]) return 1;;
13 *) echo "Please answer y or n" ;;
14 esac
15 done
16 }
17 # Shell function to get a value
18 # Usage: get_val <var_name> <message> <default_val> <list>
19 #
20 get_val()
21 {
22 while true
23 do
24 echo "$2 [$3] : \c"
25 read val
26 [ "$val" = "" ] && val=$3
27 for i in $4
28 do
29 [ "$val" = "$i" ] && break 2
30 done
31 echo "**** Invalid choice $val, must be in $4"
32 done
33 eval "$1=$val"
34 }
35 # main program
36 get_val INTR "Interrupt Number" 10 "2 3 4 5 7 10 11 12 14 15"
37 get_val PORT "I/O Base Address" 320 "200 210 220 230 300 310 320 330"
38 get_val BAUD "Baud Rate" 9600 "2400 9600 14400 33600 64000"
39 echo "Interrupt $INTR, I/O base address $PORT, Baud rate is $BAUD"
40 get_answer "Do you want to install WanCom adapter driver" && {
41 echo "Please Wait ...\c"
……
85 }
$ ./WanCom
Interrupt Number [10] : 22
**** Invalid choice 22, must be in 2 3 4 5 7 10 11 12 14 15
Interrupt Number [10] : 14
I/O Base Address [320] :
Baud Rate [9600] : 33.6
**** Invalid choice 33.6, must be in 2400 9600 14400 33600 64000
Baud Rate [9600] : 33600
Interrupt 14, I/O base address 320, Baud rate is 33600
Do you want to install WanCom adapter driver? (y/n) y
Please Wait ...
...
$
【例6-51】shell腳本程序中-x開關的作用。
腳本程序中打開-x開關,可以觀察到程序執行的流程。
$ cat chmod1
set -x
echo "$*"
for i
do
[ -f $i ] && {
chmod a+r $i
eoho "$i is readable"
}
done
$ ./chmod1 a*
+ echo a1 aa8 abcd
a1 aa8 abcd
+ [ -f a1 ]
+ chmod a+r a1
+ echo a1 is readable
a1 is readable
+ [ -f aa8 ]
+ [ -f abcd ]
+ chmod a+r abcd
+ echo abcd is readable
abcd is readable
本例中第一行的set -x也可以省略,使用命令sh -x chmodl a*也能達到相同的效果。或者,將腳本文件chmod1的第一行修改為!/bin/sh -x也可以。
【例6-52】交互式shell中-x開關的作用。
交互式shell中打開-x開關,可以觀察shell的命令替換,變量替換,文件名生成,以及轉移符的作用。每次shell在真正執行一個命令之前都把要執行的命令顯示出來。
$ set -x
$ tty
+ tty
/dev/tty6
$ termno=`expr \`tty\` : /dev/tty\\\\\\(.\\*\\\\\\)`
+ + tty
+ expr /dev/tty6 : /dev/tty\(.*\)
termno=6
$ echo $termno
+ echo 6
6
$ find $HOME -size +100 \( -name \*.c -o -name xxxx \) -exec rm -i {} \;
+ find /usr/jiang -size +100 ( -name *.c -o -name xxxx ) -exec rm -i {} ;
【例6-53】shell的-u開關的使用。
可以檢查出引用變量時的變量名拼寫錯誤。
$ name=CSPTF
$ echo Connecting to $nmae Network
Connecting to Network
$ echo Connecting to $NAME Network
Connecting to Network
$ set -u
$ echo Connecting to $nmae Network
nmae: 0402-009 Parameter is not set.
$ echo Connecting to $NAME Network
NAME: 0402-009 Parameter is not set.
$ echo Connecting to $name Network
Connecting to CSPTF Network
$
【例6-54】使用set命令設置shell的位置變量。
使用set命令可以重新設置shell的位置變量,先前的位置變量全部被新的值代替。
$ echo $#
0
$ date
Sun Jul 28 11:00:40 BEIJING 2004
$ set `date`
$ echo $1 $2 $3 $4
Sun Jul 28 11:00:40
$ ls -l /etc/motd
-rw-r--r-- 1 root staff 316 Jan 5 08:42 /etc/motd
$ set `ls -l /etc/motd`
sh:-rw-r-r-: bad option(s)
$ set -- `ls -l /etc/motd`
$ echo $9:$5 $1
/etc/motd:316 -rw-r--r--
注意--的使用,顯式地指定set命令選項的結束,否則set會把以減號開頭的命令行參數理解成set的選項。關於--,在前面的4.4.4節介紹過。
【例6-55】逐個打印源程序文件。
打印多個源程序文件,每打印個文件之前列出文件名,打印文件時每行帶上行號。
$ cat prt
while [ $# -gt 0 ]
do
echo ===================
echo FILE NAME: $1
echo ===================
awk '{printf("2d %s\n",NR,$0)}' $1
shift
done
$ ./prt makefile *.[ch]
【例6-56】給出若干個程序名,終止這些程序文件啟動的所有進程。
獲取程序啟動的進程的PID的方法,在前面的“元字符”6.5.10節中介紹過。這裡的腳本程序中使用了位置變量和shift命令。
$ cat k
while [ $# != 0 ]
do
echo "$1: \c"
PID=`ps -e | awk "/[0-9]:[0-9][0-9] $1\\$/{printf(\\"%d \\",\\$1)}"`
if [ -n "$PID" ]
then
echo kill $PID
kill $PID
else
echo No process
fi
shift
done
$ ./k myap findkey sortdat
myap: kill 26506 38020
findkey: kill 31542
sortdat: No process
$
【例6-57】軟件安裝時調整操作系統內核的部分參數。
下面的一段腳本程序,在SCO UNIX系統中安裝某個軟件包時,調整操作系統內核參數。第一行和第二行列出了相應的參數和期望的配置值。例如:要求MSGMNB參數取值至少32768, NQUEUE參數取值至少64。
命令configure的格式:
configure -y 參數名
configure 參數名=參數值 參數名=參數值 ……
第一種格式,打印出指定名字的內核參數的當前取值,第二種格式,調整指定名字的內核參數為指定的參數值,並且可以一次調整多個參數。這個命令是SCO UNIX專用的,在其它UNIX中沒有通用性。
腳本程序使用set命令和shift命令對位置變量的影響。內核參數當前取值已經滿足要求的參數不再調整。
1 PARA="MSGMNB MSGTQL MSGSEG NBLK4096 NBLK2048 NBLK1024 NQUEUE"
2 VAL=" 32768 600 16384 16 128 100 64"
3 CHANGE=
4 cd /etc/conf/cf.d
5 set $VAL
6 for i in $PARA; do
7 x=`./configure -y $i`
8 if [ $x -lt $1 ]
9 then
10 echo "Adjusting parameter $i from $x to $1"
11 CHANGE="$i=$1 $CHANGE"
12 fi
13 shift
14 done
15 if [ -n "$CHANGE" ]
16 then
17 ./configure $CHANGE
18 fi