Chapter 11 Shell 和 Shell Script

壹. 認識 SHELL

所有的電腦都是由硬體和軟體構成的﹐而負責主要運算的部分就是所謂作業系統的核心(kernel)﹐kernel 必須能夠接受來自鍵盤的輸入﹐然後交由 CPU 進行處理﹐最後將執行結果輸出到螢幕上。

比方說﹐輸入 pwd 命令﹐我們知道這是 print working directory 的意思﹐但作為 kernel 來說﹐它並不知道 pwd 是什麼﹐這時候﹐shell 就會幫我們將 pwd 翻譯為 kernel 能理解的程式碼。所以﹐我們在使用電腦的時候﹐基本上就是和 shell 打交道﹐而不是直接和 kernel 溝通。

從字面來解析的話﹐shell 就是“殼”﹐kernel 就是“核”。shell 就是使用者和 kernel 之間的界面﹐將使用者下的命令翻譯給 kernel 處理﹐關係如下圖﹕

我們在 shell 輸入一個命令﹐shell 會嘗試搜索整個命令行﹐並對其中的一些特殊字符做出處理﹐如果遇到 CR 字符( Enter ) 的時候﹐就嘗試重組整行命令﹐並解釋給 kernel 執行。而一般的命令格式(syntax)大致如下﹕

# command [-options] parameter1 patrameter2 ...

Linux 的 kernel 只有一個﹐但 kernel 之外的 shell 卻有許多種﹐例如 bourne Shell、C Shell、Korn Shell、Zsh Shell、等等﹐但我們最常接觸到的名叫 BASH (Bourne Again SHell)﹐為 GNU 所加強的一個 Bourne shell 版本﹐ 也是大多數 Linux 套件的預設 shell 。不同的 shell 都各自有其不同的優缺點﹐有興趣您可以自行找這方面的資料來看。

1. BASH(Bourne Again SHell)- Shell

BASH 之所以會被各大 Linux 套件採用為預設的 shell﹐除了它本身是 open source 程式之外﹐它的強大功能應該是吸引大家目光的重要因素之一。BASH 的功能很多﹐下面只列舉其中一少部份而已﹕

命令補全功能﹕
當您輸入命令的時候﹐您可以輸入目錄或檔案的開首字面﹐然後按‘tab’鍵將您的命令路徑補全。

例子: 您要 ls 一下 /etc/sysconfig 這個目錄的內容(假設您已經在 /etc 目錄下了)﹐您可以只輸入 ls sy 然後接連按兩下 tab 鍵﹐然後就會將 /etc/ 目錄下所有以 sy 開頭的檔案和目錄顯示出來﹐您或許可以看到 sysconfig、sysctl.conf 、syslog.conf 這三個結果﹔如果您只輸入 ls sys 再按兩下 tab 的話﹐結果是是一樣的﹐因為在 /etc/ 目錄下面﹐所有以 sy 開頭的檔案﹐第 3 個字面都是 s 而沒有其它字面了﹔如果您輸入 ls sysc 再重複這個動作﹐那麼顯示結果就剩下 sysconfig 和 sysctl.conf 而已﹐因為以 sysc 開頭的只有這兩個檔﹐如果您再按 ls sysco 接一個 tab﹐那就會幫您將 sysconfig 這個唯一以 sysco 開頭的檔案補全。

同樣的﹐這個功能也可以用在輸入命令的時候﹐比方說﹐您要輸入 Xconfigurator 命令﹐那您只需輸入 Xc 然後按一下 tab 就可以了﹗

 

命令記錄表﹕
每次您輸入一個命令﹐並按 Enter 執行之後﹐這個命令就被存放在命令記錄表(command history)中﹐而每個命令都有一個記錄號碼﹐您可以用 history命令來看看當前的命令歷史表。這樣﹐您只要用向上方向鍵﹐就可以依次呼叫出您最近所輸入的命令﹐按下方向鍵則退回最新的命令﹐找到您想要重新輸入的命令﹐然後再按 Enter 即可。

Bash 會將您登錄之後的所有命記錄在記 cache 裡面﹐然後﹐只要您成功退出這個 shell 之後﹐那這些記錄就會存放到家目錄的 ~/.bash_history 這個檔裡面(它是以 . 開頭的檔案哦﹐也就是隱藏檔是也﹐您要用 ls -a 才看得到。) 不過﹐這個檔只保持一定數量的命令記錄而已﹐您可以透過 $HISTFILESIZE 這個變數﹐來獲得或改變檔案的記錄數量。

 
強大的 script 能力:
玩過 DOS 的朋友﹐一定會知道 batch 檔案的功能﹐在 BASH 本身可以幫您執行一系列根據條件判斷的命令。在本章的後面部份﹐會詳細討論 shell script 的基本技巧。

 

貳. Shell Script

當我們對 shell 命令行有一定認識之後﹐就可以嘗試寫自己的 shell script

在 linux 裡面的 shell script 可真是無處不在﹕我們開機執行的 run level 基本上都是一些 script ﹔登錄之後的環境設定﹐也是些 script ﹔甚至工作排程和記錄維護也都是 script 。不妨到 /etc/rc.d/init.d 裡面找個程式看看﹐會發現它們的第一列都有共同的提示, 如下列所示﹕

#!/bin/sh (或者是 #!/bin/bash)

這裡的 #! 後面要定義的就是命令的解釋器(command interpreter)﹐如果是 /bin/bash 的話﹐那下面的句子就都用 bash 來解釋﹔如果是 /usr/bin/perl 的話﹐那就用 perl 來解釋。不同的解釋器所使用的句子語法都不一樣。

若Shell Scirpt的第一行開頭缺少符號"#!"加上所用Shell名稱,執行時可能會遭遇到不可預期的錯誤。

簡單來說﹐shell script 裡面就是一連串命令行﹐再加上條件判斷、流程控制、迴圈、和參數等。和我們在 shell 裡面輸入命令一樣﹐shell script 也有這樣的特性﹕

一個良好的 script 作者﹐在程式開頭的時候﹐都會用註解說明 script 的名稱、用途、作者、日期、版本、等信息。

shell script 檔的命名沒一定規則﹐可以使用任何檔案名稱﹐如果您喜歡的話﹐可以用 .sh 來做它的副檔名。

Shell Script是一種"Free Format"的程式,除了控制迴圈須注意結構完整性外,程式本身並無特殊的格式。下面是一個Borne Shell的例子:

#!/bin/sh
echo " This is a Borne Shell Script "

意: 為使你的Shell Script可以執行,你必須將這個Shell Script的執行權限"x"打開 : chmod +x shell_script

您可以在執行一個 shell 之後﹐再進入另外一個 shell (也就是啟動一個子程式)﹐然後還可以再進入更深一層的 shell (再進入子程式的子程式)﹐直到您輸入 exit 才退回到上一個 shell 裡面(退回上一級的父程式)。

關於變數

  1. 設定變數方式為
    var=value
     
  2. 取用變數的方法為
    $var

    Exmaple 1:
    #!/bin/sh
    arthur ="
    我的小名"
    echo $arthur
     
  3.  (local)變數的設定,會在在同一個process中持續存在,一直到此process結束。
     
  4.  Shell中,變數的值可以含有space的字串,帶有一個以上space的的變數,給定值的時候,必須以成對的雙引號( " )或單引號(')涵蓋之。
     

  5.  雙引號( " )中若含有變數($var),會先將變數轉換成其實際的值,單引號(')則會將$var當成是一個值,而不會作轉換動作。

    Example 2:
    1. echo "arthur
    就是 $arthur"
    2. echo 'arthur
    就是 $arthur '

    結果:
    1. arthur
    就是 我的小名
    2. arthur 就是 $arthur

     
  6.  Borne Shell中的內定變數
變數 變數意義
$# Number of arguments on the command line. 傳給shell的參數數目
$? Exit value of the last command executed. 上一個執行指令的結果值

( in general, 0代表執行無誤,其它值代表指令有誤)

$$ Process number of the current process
$n Command line 中的參數,$0代表指令名稱,$1代表第一個參數,$2代表第二個參數....
$* Command line 中的所有參數


Example 3:
Shell Script : var_test
#!/bin/sh
echo $#
echo $?
echo $$
echo $0
echo $1
echo $2
echo $*

執行 var_test 1 2 3 4 5
結果? (Please, Try it !!)

訊息列印

 echo 指令可以讓你一次在銀幕上列印出一行字串(with Double quote『"』 or Single quote『'』),以下的方式可以讓你一次印出一段文章:

Example 4:
#!/bin/sh
var1="
變數一"
var2="
變數二"
cat << SEGMENT1
Strings between the two "SEGMENT1"s will be treated as
constant
and printed out from the monitorVariables between
these two SEGMENT1's will be calculated before any processing

Yo umay try the following
var1=$var1
var2=$var2
SEGMENT1

這個程式執行的結果為:
Strings between the two "SEGMENT1"s will be treated as
constant
and printed out from the monitorVariables between
these two SEGMENT1's will be calculated before any processing

Yo umay try the following
var1=變數一
var2=變數二

Another Example:
cat << \SEGMENT2 ##<--請注意這行中的符號『\
這是避免變數被解釋的方法,請注意義以下字串被列印
出來的結果:
var1=$var1
var2=$var2
SEGMENT2

 

Another Example:
這是避免變數被解釋的方法,請注意義以下字串被列印
出來的結果:
var1=$var1
var2=$var2
 

read - 從銀幕讀入一個變數

Example 5:
#!/bin/sh
echo -e "Please input your name: \c" #(\c
迫使游標不換行)
read name
echo "Hello, $name"
 

 if 指令

Borne Shell中,if指令的用法為
if condition
then
command(s)
[elif condition
then command(s)]
[else
command(s)]
fi

括號([])表示可有可無。

 Example 6:
#!/bin/sh
ls -l /etc/host.conf
if test $? -gt 0  

then
echo "/etc/host.conf is not there !!"
else
echo "/etc/host.conf is there !!"
fi

註[$var -op value]可以比對$var與value ($var為數值變數)。op的值有gt (greater then),eq(equal to ),lt(less than),ge (greater than or equal to ),ne(not equal)及le(less than or qual to)。
 

test指令 (or [])

  1.  if控制迴圈中的condition值只有真(回傳0)與偽(回傳非0)兩種,test指令(或者用 [ ])提供了判斷真與假的功能。

  2.  test可提供檔名、字串與數值等真與偽的判斷
    檔名
    test -options file_name or [-option file_name]
    常用option與其所代表的意義如下:
    -r
    True if file exists and readable
    -w
    True if file exists and writeable
    -x
    True if file exists and executable
    -f
    True if file exists and is a regular file
    -d
    True if file exists and is a directory
    -u
    True if file exists and is setuid
    -s
    True if file exists and is greater than zero in size

字串
 test -option string or [-option string]
常用option與其所代表的意義如下:
-z
True if string length is zero
-n
True if string length is non-zero
test string1 = string2 or [string1 = string2]
* True if string1 is identical to string2
test string1 != string2 or [string1 != string2]
--> True if string1 is not identical to string2


數值
 test n1 -op n2 or [n1 -op n2]
常用op(運算元)與其所代表的意義如下:
-eq
True if n1and n2 are equal
-ne
True if n1and n2 are not equal
-gt
True if n1 is greater than n2
-ge
True if n1 is greater than or equal to n2
-lt
True if n1 is less than n2
-le
True if n1 is less than or equal to n2
** Both n1 & n2 are intergers
 

Case 指令(Conditional Swith)

case 指令的語法:
case variable in
pattern1[|pattern1a]) commands ;;
pattern2) commands;;
....
*) commands ;;
esac

Example 7:
#!/bin/sh
cat << EOF
****************************************
a.I need a banana.
b.I need an orange.
c.I need an apple.
****************************************
EOF
read choice
case $choice in
a|A) echo "You need a banana !!" ;;
b|B) echo "You need an orange !!" ;;
c|C) echo "You need an apple !!" ;;
*) echo "Bad choice, see yo next time !!";;
esac
 

while 指令

while 指令語法
while condition
do
commands
done
##迴圈中commands會一直被重複執行直到condition 的值為偽為止。

Example 8:計算從1加到10並把結果印出
#!/bin/sh
NO=11
START=1
SUM=0
while test $NO -gt 0
do
SUM=`expr $START + $SUM`
START=`expr $START + 1`
NO=`expr $NO - 1`
done
echo "The answer is $SUM"


說明:
1. Shell中把所有變數均當成字串,因此進行整數運算時,必須特別註明。內建function "expr"可以幫我們進行這樣的數值計算。
2. 符號 ` 代表在變數給定的運算式中,帶有指令或函數。設定變數必須先行運算,再做設定。例如
HOSTNAME=`/bin/hostname`
則$HOSTNAME的值會是指令/bin/hostname執行的結果

 


Example :9 以下的例子是一個交談式的新帳號建立程式

#!/bin/sh
ID=`whoami`
if [ $ID != "root" ]
then
    cat << SEGMENT1
   
    ***********************************************
     
               你不是系統管理者!!    
        
      這個程式以交談式方式幫你建立使用者帳號,
   
你必須具備SuperUser權限。    
      
    ***********************************************
    
    SEGMENT1
    exit 1
fi
    
echo -e "\n\n
請輸入使用者帳號:\c"
read login
echo -e "\n
請輸入使用者姓名:\c"
read comment
echo -e "
請輸入使用者密碼(你將看不到你的輸入):\c"
password=`./inp`
    
# "inp"
Woody老師寫的一隻小小程式,功能在避免
# 使用者key-in文字在銀幕Show出,Source請參見nmc anonymous
# ftp linux
目錄下的inp.cCompile with " cc -o inp inp.c "

echo -e "
請再輸入一次:\c"
password1=`./inp`

if [ $password != $password1 ]
then
    echo "
兩次輸入密碼不一致,請再試一次"
    exit 1
fi

adduser -c $comment $login

if [ $? -eq 0 ]
then
    echo "Account $login Created !"
    echo "$login:$password"|chpasswd
    
    #chpasswd
是系統指令,讀入"username:password" Pair(From File),將帳號"username"的密碼改成"password"

else
    echo "Account Not Created !! "
fi

echo"";echo ""

 



Exercise:
1. 請將講義Example 1~ 8親自做一次,並確實了解其語意(Know HOW)。
2. token=super
   請問下面指令會得到什麼結果?你知道為何如此嗎?
   echo $tokenman
   echo '$token'man
   echo "$token"man
3. 請寫一shell script
   (1).提示使用者選擇1.可連(telnet)至home.cyut.edu.tw 2.可連(telnet)至bbs
   (2).從鍵盤(keyboard)讀取變數『Choice』
   (3).IF Choice=1 --> telnet to home.cyut.edu.tw
        IF Choice=2 --> telnet to bbs
        Otherwise Say something to user, and terminate the program
4. 請寫一個shell script:
   (1).提示使用者輸入username
   (2).若此user存在,輸出『此使用者存在,請輸入下一筆資料』
   (3).若此user不存在,輸出『此使用者不存在,請重新輸入』
   (4).若輸入為特別鍵,則跳離此一程式。
   提示:
   a. grep username /etc/passwd可檢查該user是否存在。
   b. echo $?可以檢查上一個指令是否執行成功。
  

延伸閱讀

1. BASH Shell深入說明:  http://www.gnu.org/software/bash/bash.html