博客首页 注册 建议与交流 排行榜 加入友情链接
推荐 投诉 搜索: 帮助

darrenshen

   darrenshen.cublog.cn
关于作者  
姓名:Darren Shen
职业:Programming
年龄:跟智慧一樣年年增加中

个性介绍:

我的分类  




UNIX Shell Programming
 
Chap 0 -- 前言 ( UNIX Shell Programming not for guru ...... )


(0.1) 讀者需要那些基礎 ?

這篇文章的主題是 UNIX Shell Programming . 閱讀這篇文章須要一點
基礎 , 包括 : 對 UNIX 的命令要有一點認識 , 尤其是常用 , 重要的
命令 . 此外 , 你也必需會在 UNIX 上使用 editor , 像 vi , joe 或
emacs 等 ...... 當然 , 假如你有寫程式的經驗 , 那麼 UNIX Shell
Programming 對你來說不過是反掌折枝罷了 .


(0.2) 使用那一種 Shell ? Why ?

既然 Shell 有很多種 , 我們所要介紹是最早由 AT&T Bell Labs 的
Stephen Bourne 所寫的標準 Shell , 也就是所謂的 Bourne Shell .
在一些情況下 , 也會順便提一下 C Shell 相關的東西 .

既然現在已經有了更新更好的 Shell , 那我們為什麼還要介紹 Bourne
Shell 呢? 這是因為 , 在 UNIX Shell Programming 中 , 有許多都
還是用 Bourne Shell 的語法寫成的 , 尤其像 /etc/rc.X ( SunOS )
而一般使用者或許用 C Shell 的增強版 tcsh 或 bash ( Bourne Again
Shell ) 這些比較新的 Shell . 不過沒有關係 , 只要系統中有 sh
( Bourne Shell ) 這個直譯器存在 , 那麼 , 我們都可以用 Bourne
Shell 的語法來寫程式 . ( 沒有 sh 的系統 , 是很難想像的 ...... )
還有一點 , 用 sh 來寫程式實際上比用 csh 要好 , 網路上已經有
討論過了 , 很多書也都提到用 csh 寫程式的缺點 , 並建議不要用
csh 來寫程式 . 我個人沒有什麼意見 , sh 與 csh 就好比程式語言
中的 Pascal 與 C (只是比喻) , 想必學過這兩種語言的人都知道 ,
只要學會了其中一種 , 另一種也不算太難學 , sh 與 csh 也是一樣 .
況且寫程式能真正作事就可以 , 不需要一下子用 sh , 一下子用 csh
來寫程式 .

*************************************************************

注意 !!!! 所有的例子都要在 Bourne Shell 系列的環境下操作
建議先看看 2.3 , 才不會搞了半天 , 才發覺原來你是用 C Shell
系列的 Shell (Shell 的種類及系列可參考 2.3)
此外 , 文中的一些例子都是在 Linux 下操作 , 在 SunOS 或 HP
上 , 命令也許有少許的不同 . 當你在演練時 , 假如不能正確的
執行 , 你應該看看指令的 manual . 像 SunOS 的 find 與 Linux
的 find 就有差異 . 大部份的例子都可以在各種不同的平臺上執行 .

*************************************************************


(0.3) 本篇文章的架構

本來想用 html 來寫的 , 這樣似乎比較好 , 但是覺得有點麻煩 , 所
以還是用平常的格式來寫 , 各位假如用 joe , 那麼當看到 (參考 x.y)
時 , 你還是可以用 ctrl-k f 來尋找 x.y 的標題 ; 在 vi 下也可以用
/x.y 來尋找 , 這樣也許也可以達到 hypertext 的效果吧 ...... :)

Chap 1 -- 簡介
Chap 2 -- 基本知識
Chap 3 -- Shell Programming 中的資料表示
Chap 4 -- Shell Programming 中的語法
Chap 5 -- 讀取資料及程式偵錯
附錄一 -- Shell Summary 及 一些 Shell Script 的例子
附錄二 -- grep 簡介及 regular expressions
參考資料 -- 介紹一些相關的書及資料


(0.4) 假如你已經對 Bourne Shell Programming 很熟 ......

那也許附錄一 Shell Summary 會對你有複習及快速參考的好處 !
還有 , 參考資料中的書目 , 都是值得一看的書 , 假如你還沒看過 ,
那我想你可以參考看看 . 當然 , man 及 grep 的配合 , 也常常
可以助你一臂之力 .




Chap 1 -- 簡介


無疑的 , UNIX 是一個功能強大的作業系統 , 它多人多工特性 , 網路
的支援 , 多采多姿的 X Windows System , 都相當的吸引人 . 除此之外
UNIX 還有一項重要的特性 , 就是它提供了數以百計的命令 .
每個人都知道 , DOS 下面也不過二三十個命令 , 這二三十個命令可能
一兩天就可以學的很好了 ; 但 UNIX 作業系統則不然 , 數百個命令就算
都學會了 , 若我們不知道如何去 "組合這些命令" , 那我們將失去學習
UNIX 的最大樂趣 !!

有人會覺得疑惑 : "組合命令"會有趣嗎 ? 什麼叫作 "組合命令" ?
這就是我們所要學習的 . 現在就舉一個最簡單的例子而言 : 請計算目前
工作目錄下共有多少個普通檔案 ( Regular File ) ?

當你遇到這樣的問題 , 你要如何解決 ? 有人會說 : " 我用 ls -l 列
出目前工作目錄下所有的檔案 , 然後慢慢的數 ...... " , 假如你用了
這樣的方法 , 而目前工作目錄下又有數百個檔案 , 那麼 , 我真的很敬
佩你 "愚公移山" 的精神 . 還有些人比較 "暴力" , 他說 : " 這個嘛
很簡單 , 我寫一個 C 語言程式 , 用 opendir , lstat 等函數來計算
某一目錄下有多少個普通檔案 ...... " 假如你要這樣做 , 那我當然不
反對 , 常練習寫程式也是不錯的 . 然而 , 就為了這麼小小的一個問題
而寫一段 C 語言的程式 , 未免太小題大作 . 實際上 , 解答的關鍵就
在 : " 你是不是會從數百個 UNIX 的命令中挑出適合的命令 , 然後把
這些命令組合起來 , 以達到我們所要的目的 !! " . " UNIX 的每個命令
就好比各種不同的 IC , 每種 IC 都有它特殊的作用 , 藉由不同 IC 的
組合 . 我們可以達到我們所要的功能 " , " 要達到目的的方法 , 常常
不是只有一種 ......" 上面這幾句話相當重要 , 各位現在也許還不清
楚 . 不過上面幾句話所提的 , 的確就是 UNIX 頗為吸引人的地方之一 .
除了命令之外 , Shell 提供了一些類似一般程式語言的語法 , 我們可
以利用這些語法來寫程式 . 而這點也正是本篇文章的最主要內容 .




Chap 2 -- 基本知識


(2.1) 什麼是 Shell ?

我們可以把 UNIX 切成兩方面來看 , 一部份是系統的核心 (kernel) ,
另一部份就是系統中的應用程式 . 舉凡我們常用的一些命令 , 像
ls , find 與 Shell 一樣 , 都可被歸類為應用程式 . 所以 , Shell
的地位與其它的應用程式並沒有什麼差別 .

我們可以看看簽入 UNIX 作業系統的過程 : 當我們輸入 username 之
後 , getty 就結束 , 取而代之的是 login . 在我們輸入正確的
password 後 , 系統就依照 /etc/passwd 中的記錄 , 為使用者啟動
相對應的 Shell . 在這個時候 , 使用者就會得到一個提示符號 .
接著我們就可以下達一些命令 .


--  getty --> login --> shell --> logout --
^                                          |
|------------------------------------------


Shell 有另一個名稱叫做 "命令列直譯器" (command interpreter)
從這個名稱當中 , 我們就可以了解到 Shell 實際上為命令列的翻譯官
當我們在提示符號後輸入一長串的命令 , 並按下 Enter 之後 , Shell
就將我們所輸入的東西 , 做一個適當的分析及處理 .


(2.2) Shell 負責的事

對系統做交談性的使用 . 包含了輸出輸入導向 ( 參考 2.5 ) ,
管線 ( 參考 2.6 ) Wildcard 的展開及匹配 ( 參考 2.4 ) , 另外
還有 Job Control( 從 SVR4 開始) , 變數環境設定?. 解譯命令 ....
還有一點很重要 , Shell 有它自己內建的程式語言 , 利用它的語法
我們可以來寫程式 , 寫出來程式功能並不像一般的程式語言 ( 如 C
語言 ) 那麼多 , 然而 , 在很多情況下就夠用了 . 這部份也就是整
篇文章的重點 .


(2.3) UNIX 系統上 Shell 的種類

在 DOS 中 , 想必各位一定都知道它的 Shell 為 command.com . 特別
要說明的是 : " Shell 是可以被置換掉的 ! " 所以 , 許多人在 DOS
中 , 常常把他們的 Shell 由 command.com 換成 4dos.com , 以得到
更多的功能 , 這已經是很平常的事了 . 在 UNIX 系統中也是一樣 ,
使用 chsh 這個命令 , 你可以挑你喜歡的 Shell 來使用 . 合法的
Shell 清單被列在 /etc/shells 這個檔案中 .

Shell 的種類可以分為兩個支派 , 一為由 Bourne Shell 衍生而來的
包括了 sh (Bourne Shell) , ksh (Korn Shell) , bash (Bourne
Again Shell) , zsh (Z Shell) ; 另一支派為 C Shell 衍生而來的 ,
包括了 csh , tcsh . 現在許多人作業的環境常常是在 csh 或 tcsh
之下 , 尤其是從 BSD 衍生而來 SunOS 的環境 .

你可以使用 echo $SHELL 來獲知你現在到底使用那一種 Shell . 或
者直接看 /etc/passwd 的最後一個欄位 .


(2.4) Wildcard 的展開及匹配

      Wildcard 有些人把它翻譯成 "萬用字元" , 下面所列舉的
      項目 , 並不盡然每一種 Shell 都支援 , 各位要自己試試看 .


(2.4.1) * : 代表任意的字串 ( 字串可以是空的 )

            % ls
            haha    haha1   haha2   memo1   memo13  memo2   memo23

            % ls haha*
            haha   haha1  haha2

            % ls memo*
            memo1   memo13  memo2   memo23

(2.4.2) ? : 代表任意一個字元

            % ls
            memo1   memo13  memo2   memo23

            % ls memo?
            memo1  memo2

            % ls memo1?
            memo13

(2.4.3) []: 代表符合[]中所列舉的字元

            % ls
            haha    haha1   haha2   memo1   memo13  memo2   memo23

            % ls memo[12]
            memo1  memo2

            [a-e]bc 會符合 abc , bbc , cbc , dbc , ebc  
             

(2.4.4) [!]: [] 中的字元 , 若加了 ! , 則表示不符合[]中所列舉的字元

            % ls
            haha    haha1   haha2   memo1   memo13  memo2   memo23

            % ls memo[!1]
            memo2

            [!a-z]bc 會符合不是以小寫英文字母開頭 , 後面接著 bc
            的字串 . 如會符合 : Cbc , 6bc 等 ......  


(2.4.5) {word1,word2....} : 如 my_{dog,cat,pig} 會符合 my_dog
         my_cat , my_pig
  

(2.4.6) \ : 接在 \ 之後的特殊字元 , 其特殊的意義會被取消
            例如 , 有一個檔案 , 它的名字真的叫 *abc , 那我們得用
            cat \*abc 才能把 *abc 中的內容顯示出來 .


(2.4.7) 更多關於 Wildcard 的討論 :

   (I)  glob : 這種 Wildcard 匹配的動作 , 我們稱為 " globbing "
        在很多種 Shell 之中都有一個叫作 noglob 的變數 , 可以把
        Wildcard 展開匹配的動作關掉 .  

        % ls *bc
        abc  bbc  cbc  dbc
               
        % set noglob   <-- C Shell 中設定變數的方法
                                   ( 設定變數參考 2.7)  

        % ls *bc
        ls: *bc: No such file or directory

  (II)  * 不會包含 . 開頭的檔案 , 要符合以 . 為開頭的檔案 , 你
        可以試試 .??* 或 .[!\.]*  ( For Newer Bourne Shell )
        .[^.]*  ( For tcsh )


(2.5) 輸出輸入導向


(2.5.1) Standard Input , Standard Output and Standard Error

在某方面來說 , 把資料寫到一個檔案中與寫到終端機並沒有什麼差別
相同的 , 把資料從檔案中讀出與從鍵盤讀出亦沒有什麼差別 .
在 UNIX 系統中 , 標準輸出輸入 ( Standard I/O ) 提供了一些預設
的輸出輸入設備 , 這些就是 stdin , stdout , 以及 stderr .

stdin : 一般說來 , 就是來自你鍵盤的輸入 , 但許多情況下 , 程式
        常忽略了 stdin , 而直接在命令列的參數中拿 . 如 :
        cat haha . haha 就可以當做 stdin .
stdout : 正常訊息輸出 , 預設是終端機 .
stderr : 錯誤訊息輸出 , 與 stdout 一樣 , 預設是終端機 .

stdin 的檔案描述值 ( file descriptor number ) 是 0 ,
stdout 是 1 , stderr 是 2 .


(2.5.2) 輸出輸入導向

舉一個最簡單的例子 , 我們用 ls -la 可列出目前目錄下所有的
內容 . 然而 , 若我們想要把終端機上的輸出放到一個檔案中 ,
那我們要如何做呢 ? 這時候 , 我們就要把原來終端機上的輸出
(也就是預設的標準輸出)導向至一個檔案 . 我們可以這樣作 :

ls -la > output_file

這個 > 的符號就是導向符號 .


下表是一般經常看到的輸出輸入導向 , prog 就是 program 的意思
而 file 就是一個檔案:



      功能                    sh,ksh,bash         csh,tcsh
--------------------------------------------------------------

把標準輸出導到一個檔案        prog > file         prog > file

把標準輸出導到檔案描述值 n    prog >&n

把原來輸出至檔案描述值 m      prog m>&n
的輸出與輸出至檔案描述值 n
的輸出 , 一起導向至檔案描
述值 n

把標準錯誤輸出導到一個檔案    prog 2> file

把標準輸出關閉                prog >&-

把標準輸出以及標準錯誤輸出    prog > file 2>&1    prog >& file
導到一個檔案

把標準輸出導到 f1 , 把標準   (prog > f1) 2>f2   (prog > f1) >& f2
錯誤輸出導到 f2

--------------------------------------------------------------

把標準輸出導到一個檔案的後面    prog >> file        prog >> file

把標準錯誤輸出導到一個檔案的    prog 2>> file
後面

把標準輸出以及標準錯誤輸出導    prog >> file 2>&1   prog >>& file
到一個檔案的後面

--------------------------------------------------------------

把檔案當作標準輸入            prog < file         prog < file

從檔案描述值 n 的地方讀取     prog <&n
當作 prog 的輸入          

把標準輸入關閉                prog <&-

--------------------------------------------------------------

把 file1 當成 prog 的標準     prog < file1 > file2
輸入 , 並把標準輸出的結果
再導向至 file2

--------------------------------------------------------------

從鍵盤的輸入當作 stdin , 直     prog < file

    w > who_log

    把 w 這個指令的輸出導到一個叫做 who_log 的檔案

    cat file1 > file2
    ^^^^^^^^^
    把 cat file1 的輸出導到一個叫做 file2 的檔案

(II) prog 2> file

     先寫一個叫做 test1.c 的 C 語言程式如下 :

     #include

     void main(void)
     {
       print("Standard Error Test");   /* 故意寫錯 */
     }

     接著輸入  cc test1.c              <-- compile test1.c
     這時我們會看到類似下面的錯誤訊息:
     /tmp/cca006131.o(.text+0x1e): undefined reference to `print'
        
     從上面的情況中 , 我們可以看到 , 這個 C 語言程式只有一個錯誤
     所以得到的錯誤訊息也不多 . 然而 , 我們若 compile 一個很大的
     程式 , 錯誤也許會有數百個也不一定 , 這時候 , 若我們不採用把
     錯誤輸出導向到檔案的方法 , 那麼錯誤訊息在終端機上立刻就捲出
     去了 , 使用者就無法看的完全 . 比較好的方法是把錯誤訊息導到
     一個檔案中再慢慢分析比較好 . 所以這時候我們應該這樣做 :

     cc test1.c 2> errmsg

     此時 , 原本在終端機上的錯誤輸出就會被導到 errmsg 這個檔案中
   
     還有 , prog 2> /dev/null 可以抑制錯誤訊息的輸出 , 因為它把
     標準錯誤輸出導到 /dev/null 這個垃圾桶中 , 這個功能與上面的
     prog >&- 有相同的地方 , 本來應該輸出的訊息都被壓抑了 . 只
     不過一個是抑制錯誤訊息輸出 , 一個是抑制正常訊息輸出 . 不過
     prog 2>&- 就可以達到與 prog 2> /dev/null 相同的效果 .

(III) prog > file 2>&1

     我們可以試試下面的命令 :

     find / -size +2000k > ~/output 2>&1

     接著你可以在你的 Home Directory 下找到 output 這個檔案 , 你
     可以看看它的內容 . 你就會發覺 , 有些目錄對你來說是
     Permission denied 的 , 這些都是錯誤訊息的輸出 . 你也會看到
     有些檔案名 , 這些檔案都是大於 2000k 的 , 屬於正常訊息的輸出
     所以 , output 這個檔案的確包含著標準輸出以及標準錯誤輸出的
     訊息 .

     ** 注意 !!! SunOS 上的 find 是以 block 為單位 , 所以 , 假
     如你在 SunOS 上操作 , 那請把 +2000k 的 k 去掉 , 如下 :

     find / -size +2000 > ~/output 2>&1
    

(IV) (prog > f1) 2>f2

     再一次的 , 我們用上面的命令 , 但改為 :

     (find / -size +2000k > ~/normal_out) 2>/tmp/error_out

     在 (III) 中 , 我們把正常訊息的輸出與錯誤訊息的輸出都混在
     ~/output 這個檔案中 . 從某個角度來說 , 這不是很好的作法 ,
     我們所希望的 , 是把正常訊息的輸出放在一個檔案 , 而錯誤
     訊息的輸出放到另一個檔案 . 此時 , 我們就要採用 (IV) 這個
     方法 . 在上面的命令中 , 我們把 find 這個命令的正常訊息輸出
     導到 ~/normal_out 這個檔案 , 而錯誤訊息輸出我們就把它導到
     /tmp/error_out 這個檔案中 .
     
(V) 至於 prog >> file  ,  prog 2>> file  ,  prog >> file 2>&1
    與 prog > file  ,  prog 2> file  ,  prog > file 2>&1
    只是把 > 換成 >>  , 而它的差別就只在 :  >> 是"附加"的意思
    並不像 > 會把原來導向過去的檔案清除 (假如欲導向過去的檔案
    已經存在 , 而且 noclobber 變數沒有被設定的話 !當然 , 若原來
    檔案不存在 , 那就會製造出新的檔案)

    所以 , 試試看下面的命令就可明白 :

    ls -la /usr > file1
    ls -la /etc > file1
    cat file1       <-- 你看到的是 /etc 下的內容 
   
    接著 , 再試試

    ls -la /usr > file2
    ls -la /etc >> file2  <-- 把輸出的結果"附加"在 file2 後
    cat file2       <-- 你看到的是 /usr 與 /etc 下所有的內容
    
(VI) prog < file

    有些命令 , 它要處理的內容是要由標準輸入中拿的 . 如 mail 這
    個命令 , 若我們在提示符號下鍵入 mail abc@cc.nctu.edu.tw
    此時 , 我們就得由鍵盤上輸入一些資料 , 最後按 Ctrl-d 作為結
    束 . 此時 , 我們剛剛輸入的資料就會經過 mail 處理寄出電子郵
    件 . 然而 , 這樣作的壞處是 , 萬一你打錯了字想修改前面幾列
    的內容 , 就變得困難了 . 所們所希望的 , 是信件的內容先用
    普通好用的文書編輯器寫好 , 再把寫好的東西寄出去 . 此時 , <
    就派上用場 , 我們就把寫好的檔案當作是從標準輸入給與的內容
    導向給 prog . 所以 mail abc@cc.nctu.edu.tw < report
    report 就相當於從鍵盤上輸入的內容 .


(VII) prog < file1 > file2

    這個型式是由兩個導向符號所組成 , 看起來有點複雜 , 其實一點也
    不 . 只要你拆成兩部份來看 : prog < file1 > file2
                                ^^^^^^^^^^^^            (i)
                                ^^^^^^^^^^^^^^^^^^^^    (ii)
    第 (i) 部份 : 把 file1 當作是 prog 的標準輸入
    第 (ii) 部份 : 把第 (i) 部份的輸出結果 , 導向至 file2 中
    舉個例子 :

    tr 'a-z' 'A-Z' < sour > dest

    首先 , tr 'a-z' 'A-Z' < sour  會把 sour 這個檔案中的小寫字元
    全部換成大寫字元 , 此時 , 假如沒有後面 > dest 的話 , 我們會在
    終端機上看到轉換後的輸出 . 然而 , 我們使用 > dest 再把原來應
    該在終端機上的輸出導向到 dest 這個檔案中 . 所以 , dest 就會
    是 sour 經過轉換後的內容 .

(VIII) prog << c

    這個型式看起來有點奇怪 , 它所作的事就是 : 從標準輸入讀取資料
    一直遇到 c 為止 , 然後把這些資料都交給 prog 處理 .
    這種動作有個名稱叫 "Here Document"
    比如說下面的例子 :

    % mail veronica << End_Of_Letter
    > this is a test
    > but ....
    > End_Of_Letter             <-- 要單獨一列

    本來我們輸入 mail veronica 的話 , 那麼我們就得從鍵盤上輸入
    資料 , 直到按下了 Ctrl-d 為止 . 然而 , 我們現在指定 : 一
    直從標準輸入讀取資料 , 直到遇見 End_Of_Letter 為止 . 不過
    上面的例子並不常見 . 比較常見的是下面這個例子 :

    % cat << It_is_OK
    > This is Here Document Test
    > This Sample is quite typical
    > You will see the same style in some Shell Script
    > See You
    > It_is_OK

    如此作可以一次顯示一大段訊息 , 而不必很麻煩的用許多列 echo           
    來做 .


(2.6) 管線 (Pipes)

管線就是達成 "組合命令" 的方法 , 極端重要 ! 它的型式是這種樣子

  prog1 | prog2

其中 , | 就是管線的符號 , 上面的意思是 : 把 prog1 的輸出 , 當
做 prog2 的輸入 . 你也可以想像成 : prog1 及 prog2 是兩臺機器 ,
| 是它們之間的傳送帶 , 經過 prog1 處理過的東西 , 再交給 prog2
去加工 . 舉幾個例子 :

  ls -la | more   <-- 把 ls -la 的輸出交給 more 去作處理 , 想
                      當然耳 , more 會把來自 ls -la 的輸出作
                      分頁處理 .

  man ls | wc -l   <-- 把 man ls 的輸出交給 wc 去作處理 , wc
                       使用選項 -l 可計算來自 man ls 的輸出
                       有多少列 .

當然 , 你也可以組合出兩個以上或更多的管線 :
    
  prog1 | prog2 | prog3

如:  who | cut -c1-8 | sort   <-- who 的輸出交給 cut 處理 ,
                                  cut -c1-8 會取出每列的第
                                  一到第八個字元 , 然後再把
                                  這些輸出交給 sort 做排序

所以 , " 熟悉每個命令要的輸入是什麼 ? 又會產生什麼輸出 ? "
是有效使用管線的不二法門 ! 各位應該一邊使用管線 , 一邊加強
命令的基礎 , 當你熟悉到了某一程度 , 就可以自己利用管線 , 輸
出輸入導向來 " 創造 " 出新的工具 , UNIX 有趣且重要的地方就
在這裡 !!!
                                  
此時 , 我們在 Chap 1 所提的 : " 計算目前工作目錄下有多少普
通的檔案 ? " 假如你對命令熟悉的話 , 你應該可以回答出來了 ,
而且方法太多種了 !

像我可以舉一種方法如下 (不計算以 . 為開頭的隱藏檔) :

  ls -l | grep '^-' | wc -l

各位去分析看看 , 為什麼可以計算出來 , 並且去想想看它是如何
工作的 ......


(2.7) 一些環境變數的指定
 
在 Shell 中 , 有一點很重要的是 , 我們自己可以設定一些變數 ,
其中有些變數可以影響操作環境的行為 , 或者是影響應用程式 .
舉個例子來說好了 , 各位常用的 elm , 它所使用的文書編輯器
就會受到 EDITOR 這個變數的影響 . 假如 EDITOR 的內容是 joe ,
那麼 elm 就會使用 joe 拿來當文書編輯器 , 假如 EDITOR 的內
容是 vi , 則使用 vi . 既然我們知道某些變數可以影響應用程式
甚至整體的操作行為 , 那我們要如何設定這些變數呢 ? 請看下表


                sh,ksh,bash                csh,tcsh
-----------------------------------------------------------

指定變數        var=value                  set var value

指定環境整體    export var=value           setenv var value
變數

-----------------------------------------------------------


上表中 , var 是變數名 , value 是變數值 ; 還有
上面兩種設定是不一樣的 , 因為 Shell 之中還可以再 invoke 一
個 Subshell , var=value 只能算是設定區域性的變數 , 只在設定
的那個 Shell 之中有效 , 對於 Subshell 就無效了 .
而 export var=value 就不一樣了 , 它對於 Subshell 也有效 .
你可以做下面的實驗 :

  % color=blue              <-- 把 blue 指定給變數 color ,
  % echo $color             <-- 顯示變數 color 的內容
  blue             
  % sh                      <-- 開啟一個 subshell
  % echo $color             <-- 顯示變數 color 的內容
                            <-- 沒有變數 color 的內容
  %


  % export color=blue       <-- 把 blue 指定給環境整體變數 color
  % echo $color             <-- 顯示變數 color 的內容
  blue
  % sh                      <-- 開啟一個 subshell
  % echo $color             <-- 顯示變數 color 的內容
  blue                      <-- 有變數 color 的內容


前面在 Wildcards 有提過一個 noclobber 的變數 , 在屬於 Bourne
Shell 系列的 Shell 中 , 你可以使用 set -o noclobber 或者是
set +o noclobber 來使 noclobber 發生或不發生作用 . 使用 set -o
不加變數名可看到一些變數的值 ( 只能看到那些為 on 或 off ,
對環境會造成影響的變數 ) 其它的變數可用 set 直接看到 .
在 C Shell 系列中 , 直接使用 set noclobber 就可以了 .
至於有那些變數可設定 , 你可以用 man 看看你所使用的 Shell .




Chap 3 -- Shell Programming 中的資料表示

截自目前為止 , 我們都是處於在提示符號後下命令的階段 . 並沒
有 "寫程式" 的感覺 . 在這一章當中 , 我們就要開始介紹 Shell
Programming 中的資料表示方式


(3.1) 什麼是 Shell Script ?

假如你使用 UNIX 已經有一段時間 , 那你一定常常聽到這個名詞 .
既然前面提過 , Shell 本身就是一個 interpreter , 那我們當然
可以在提示符號下 "直接" 寫程式的 ! 可以看看下面的例子 :

% case 3 in
> [1-5]) echo 'haha';;
> [6-10]) echo 'lala';;
> esac
haha

你自己也可以試試看 , 不過你得使用 Bourne Shell 系列的 Shell
好了 , 我們已經看到可以在提示符號下寫程式 , 然而 , 我們並不
喜歡這樣做 . 因為在提示符號下要修改程式很麻煩 , 小的程式也就
算了 , 大的程式我想沒有人會喜歡這種方法 . 所以 , 在一般的情
況下 , 我們可以先用文書編輯器寫好一個程式並存成一個檔案 , 然
後再把這個檔案交給 Shell 去執行 , 而這個檔案就稱為 Shell
Script ! 先給各位看看一個最簡單的 Shell Script :

 
  #!/bin/sh

  ps -a | more


上面的兩列內容 , 我把它放在一個檔案中 , 然後直接執行它就可以.
但不要忘記 , 你要對這個檔案有讀以及執行的權限 .


(3.2) Shell Programming 的兩大要素 : 資料及語法 

假如你學過高階語言 , 你應該可以了解到 , 其實每種高階語言的概
念都差不多 , 它們只是語法不同 , 也許資料表示的方法也有少許的
差別 . 但是 , 學會了一種再去學別種並不算難 .
Shell Programming 也是一樣 , 它還是有大家熟悉的語法如 if , do
for , while 等等的東西 . 比較特殊的是它的資料型態 .
下面就開始進入 Shell Programming 的資料表示 . 語法請看 Chap 4


(3.3) 註解及 #!/bin/sh

很多程式語言都有其註解格式 , 在一個 Shell Script 中 , 假如我
們看到一個 # 號 , 那從 # 號開始一直到那列的結尾都是註解 .
註解只是提高程式的可閱讀度 , 對 Shell 來說並不會做解譯的動作
舉一個 Shell Script 如下:

#!/bin/sh


# Count_bash_user_number : report how many users use bash
#

grep -c '/bin/bash' /etc/passwd    # grep -c : print match
                                   # number


這個 Shell Script 中 , 真正作事的只有一列 , 其它 # 後接的文
字都是註解 . 但特別要注意的是 : #!/bin/sh 不是註解 , 雖然它
是以 # 為開頭 , 但是它有特殊的意義 . 在最早的時候因為只有 sh
所以也並不需要 #!/bin/sh , 但是後來 Shell 的種類越來越多 ,
有些人用 csh , 有些人用 tcsh . 這時候我們就要特別的指出 , 這
個 Shell Script 是用那一種 Shell 寫的 . 假如你的 Shell Script
是以 Borune Shell 的語法寫成的 , 那你就得加上 #!/bin/sh , 假
如是以 C Shell 的語法寫成的 , 那你得加上 #!/bin/csh . 這列一
定要加嗎 ? 答案是不盡然 , 如果你目前使用的 Shell 與你寫的
Shell Script 使用相同的語法 , 那可以不加 . 但是我舉一個例子:
假如你用的是 tcsh , 但卻用 sh 的與法來寫 Shell Script , 而沒有
在 Shell Script 的第一列加上 #!/bin/sh , 那麼會發生什麼情形?
很簡單 , tcsh 會以為你寫的 Shell Script 是用 tcsh 的語法寫成
的 , 所以在解譯的過程中就會出現錯誤 . 但是若我們有加 #!/bin/sh
那麼 , tcsh 就會叫用 sh 來解譯你的 Shell Script , 自然就不會
有錯誤產生 .
我的建議是 : "無論如何 , 請明確的在 Shell Script 的第一列指出
這個 Shell Script 是用那一種 Shell 的語法寫成的 !"


(3.4) 變數

(3.4.1) 變數的指定

如同大部份的程式語言一樣 , Shell Programming Language 中也有
變數的存在 , 那我們要如何指定一個變數呢 ? 很簡單 , 如下:

  variable=value

舉幾個例子 :

  count=1

  X11_bin_path=/usr/X11/bin

但在指定變數時 , 有幾點要注意的 : 第一 , 等號的左右兩邊不要
有空格 , 譬如說 , 不可寫成 : color = blue , color= blue
color =blue , 一定得寫成 : color=blue . 第二 , 變數沒有所謂
的 "資料型態" . 當你指定變數時 , 變數只是被當成字元的組合來
看待 . 如上面的例子 : count=1 , count 放的是單純的 1 , 而不
像 C 語言中所謂 "整數型態的 1" 第三 , 變數指定的動作實際上
也可以在提示符號後直接作 . ( 如同 2.7 所提到的 )


(3.4.2) 變數的內容 : $variable

使用 echo $variable 可以顯示變數的內容 .
像剛剛的例子 , count=1 , 那麼 , 使用 echo $count 就會得到 1
使用 echo $X11_bin_path 就會得到 /usr/X11/bin
所以 , 我們可以了解到 : $variable 是變數的內容 !
我現在給一個問題 , 各位練習看看 : 把 var1 的內容設定成 10 ,
把 var2 的內容設定成 20 , 接著把 var2 的內容設定給 var1 .

  var1=10
  var2=20
  var1=$var2
  echo $var1

再實際練習一下 , 下面的四列與上面的四列有何不同 ?

  var1=10
  var2=20
  var1=var2
  echo $var1


(3.4.3) 變數與 Wildcard

你可以在提示符號下這樣試試 :

  list=*
  ls $list

上面的兩列 , 首先把 * 指定給 list , 所以 $list 就是 *
那 ls $list 就會變成 ls *

  five_char=?????
  ls $five_char

上面兩列會把長度為五個字元的檔案列出 .


(3.4.4) ${variable}

有人試了 (3.4.3) 中的例子之後就想到 , 我們可不可以這樣做 :

  file=mydoc
  cp $file $filenew                  

把 mydoc 檔案複製一份 , 並把新檔案的名稱後加上 new 變成
mydocnew . 假如你按照上面的方法來作 , 會有錯誤產生 .
這是因為 , $file 固然是 mydoc 沒錯 , 然而 $filenew 卻是
一個內容不知道是什麼的變數 . 所以我們應該這樣做 :

  file=mydoc
  cp ${file} ${file}new

其中 ${file} 會換成 mydoc , ${file}new 會換成 mydocnew 


(3.5) 引號

在 Shell Programming 中 , 很特殊的一點就是引號 , 引號有三種
分別為 ' ' 單引號  , " " 雙引號 , ` ` 反單引號 . 它們分別在
Shell Programming 中扮演不同但都相當重要的角色 . 我們分別敘
述如下 :

(3.5.1) 單引號

單引號最大的用處是 : 使得兩個單引號之間所夾的內容保持不變 .
比如說你想印出 : pig   cat      dog
那假如你這樣作 : echo pig   cat     dog
那麼結果仍是 pig cat dog
要改成 : echo 'pig   cat      dog'  才能達到目地

還有 , 在單引號中的特殊符號也會失去意義 :

  % echo '* is all'   
  * is all

  % color=blue
  % echo 'Is $color beautiful?'
  Is $color beautiful?

  % echo '> < >> << [ ] "" & { }'
  > < >> << [ ] "" & { }

從上面的例子中 , 我們都可以看到 , 像 * , ? , $variable 等
本來應該會被替換的東西 , 現在都原封不動的照印 .所以 , 單引號
通常拿來印出一段固定的訊息 .

(3.5.2) 雙引號

雙引號的用法與單引號有一些不同 . 我們提過 , 在兩個單引號之間所
夾的內容都會原封不動 . 然而 , 雙引號不同 . 在兩個雙引號之中
的內容 , 有以下幾種會被替換 :

(I) $variable , 以及任何 $ 後有意義的字元 (參考 3.7)

     比較以下的不同 :

     % color=blue
     % echo $color
     blue
     % echo '$color'
     $color
     % echo "$color"
     blue

     % all_list=*
     % echo $all_list      <-- $all_list 換成 * 號
     a.c b.c
     % echo '$all_list'    <-- 單引號不替換
     $all_list
     % echo "$all_list"    <-- 雙引號中的 $all_list 換成 *
     *                         變成 echo "*" , 但雙引號中
                               的 * 號不會再做替換 .

(II) 反斜線 : \

     在 (2.4.6) 之中提到 , \ 後的字元 , 其特殊意義會被取消
     在上面提到 , $variable 在雙引號之中會被替換 . 假如我
     們真的想印出 $ 號 , 那麼就得在 $ 號前加上 \

     % number=10
     % echo "your value is $number"
     your value is 10
     % echo "your value is \$number"  <-- 取消 $ 之特殊意義
     your value is $number
     % echo "your value is \"$number\" "
     your value is "10"

(III) 反單引號 : ` ` 
     ( 參考 3.5.3 )


(3.5.3) 反單引號

反單引號是一個往右撇的引號 . 兩個反單引號所夾的內容會先執行
舉個例子 :

  % echo "Your current directory is `pwd` !"
  Your current directory is /home/jhhsu

我們說過 , 雙引號之中的內容大多不做替換 , 除了 $variable ,
反斜線 , 反單引號之外 . 上面就是一個很好的例子 . 雙引號
中的 `pwd` 會先執行 , 得到的結果是 /home/jhhsu . 所以 ,
`pwd` 被 /home/jhhsu 所取代 , 全句轉變成 :

  echo "Your current directory is /home/jhhsu"

自然就在終端機上印出 Your current directory is /home/jhhsu

再舉一個例子 , 前面在導向時有學過 : mail jhhsu < letter
這樣可以把 letter 這個檔案寄給 jhhsu .
但假如我們想寄信給一大群人呢 ? 實際上你可以 elm 中 alias
的方法 , 不過這有點麻煩 . 我提出一個很直覺的方法 :

  mail `cat member` < letter

檔案 member 中每列都寫上收信人的 E-mail address , letter
是信件內容 . 它工作的過程很簡單 , `cat member` 先執行 ,
得到的結果可能是 jhhsu veronica ghguo , 所以全句變為 :

  mail jhhsu veronica ghguo < letter


(3.6) 變數的算數運算

在變數的算數運算方面 , 我們主要靠的是 expr 這個指令 . 各位
可以先在提示符號下試試 :

  % expr 30 + 20
  50
  % expr 30 - 20
  10
  % expr 30 \* 20     <-- * 是乘號 , 但要以反斜線去除特殊意義
  600
  % expr 30 / 20      <-- 只會取整數部份
  1

從上面我們可以看到 , expr 的確可以作整數的四則運算 . 但我們提
過 , 變數是沒有資料型別的 , 所以 expr 只能處理變數內容為可視
為整數的變數 . 如 :

  % var1=10
  % expr $var1 + 1        <-- 把 var1 加 1

像下面就錯了 :

  % var1=10        
  % var2=20
  % expr var1 + var2      <-- var1 及 var2 不是內容 , $var1
  expr: non-numeric argument   $var2 才是

上面要改成 :

  % var1=10        
  % var2=20
  % expr $var1 + $var2 
 
下面是無意義的例子 :

  % var1=red
  % var2=green
  % expr $var1 + $var2    <-- expr 不能處理 red + green

那假如我們要把某一變數的內容加一再放回去呢 ?
如同 Pascal 中的 i:=i+1 , C 語言中的 i=i+1 那要如何作 ?  
很簡單 , 如下 :

  % i=`expr $i + 1` 

等號右邊的反單引號會先執行 , expr 把 i 加 1 然後再把得到
的結果指定給 i


(3.7) 命令列參數的傳遞

設計一個 Shell Script , 取得命令列的引數是相當重要的 . 舉個
例子 , 你想寫一個 Shell Script 叫做 plus . plus 需要兩個參數
然後把這兩個參數相加的結果印出 . 那你應該如何做呢 ? 又有時候
我們要判斷使用者參數的個數是不是正確 , 就像上面 plus , 總得
要有兩個參數 . 這些都要靠參數的特殊表示來達成 .

(3.7.1) $n 是第 n 個參數的內容 ,  n=0,1,2,3,4,5,6,7,8,9

在 Shell 變數指定中 , 你不能這樣做 :  1=first  , 因為數字有
其特殊意義 . 像 $1 就是第 1 個參數的內容 , $2 是第 2 個 , 餘
類推 . 那第 n 個參數的內容有用嗎 ? 就用上面 plus 來說明好了 .
先用文書編輯器寫一個 Shell Script , 並命名為 plus 如下 :

  #!/bin/sh

  echo `expr $1 + $2`

因為一般人的 umask 值常是 022 , 製造出來的檔案都是沒有執行權限
所以我們得用 chmod u+x plus 來使 plus 有執行的權限 .
接著你可以在提示符號下輸入 :

  plus 3 5  

那得到的答案就會是 8 .

再舉一個例子 : 我們要計算某個目錄下有多少個檔案 . 而我們希望這
個目錄是由使用者在命令列的參數所給與的 . 那我們可以這樣寫 :

  #!/bin/sh

  ls $1 | wc -l

以後使用者只要輸入這個 Shell Script 的名稱 , 後面接著某目錄名 ,
那麼就可以計算出有多少檔案 . ( 這個 Shell Script 把目錄也當成
檔案來看待 )

還有 , $0 就是你執行命令的名稱

(3.7.2) $# 代表參數個數 

上面的 plus 3 5  , 其中 3 是第一個參數 , 5 是第二個參數 , 我們
把上面的 plus 這個 Shell Script 加以擴充 , 變成 :

  #!/bin/sh

  echo "There are $# arguments"
  echo "The Answer is `expr $1 + $2`"

這個例子利用到我們所目前所學過的不少知識 , 包含 :

雙引號中 , $ 號會被替換 . 又因為我們有 3 5 兩個參數 , 所以 $#
被換成 2 . 因而印出 There are 2 arguments .

雙引號中 , 兩個反單引號之中所夾的東西會先執行 . 所以 ,
`expr $1 + $2` 換成 `expr 3 + 5` 最後得到結果 8 . 因而印出
The Answer is 8 .

講到這裡 , 假如各位看不懂上面的 Shell Script , 那可見對引號
的應用還不太熟 , 我建議重看 (3.5) , 並多加練習 .

我們現在再寫一個 Shell Script , 負責顯示有多少個參數 , 並且
把前三個參數顯示出來 :

  #!/bin/sh

  echo "There are $# arguments"
  echo "argument1 is $1"
  echo "argument2 is $2"
  echo "argument3 is $3"

拿這個 Shell Script 來試試 :

  % argtest red green blue
  There are 3 arguments
  argument1 is red
  argument2 is green
  argument3 is blue

  % argtest 'a b c'
  There are 1 arguments
  argument1 is a b c
  argument2 is
  argument3 is

  % argtest "* <> ?"
  There are 1 arguments
  argument1 is * <> ?
  argument2 is
  argument3 is

  % argtest *    
  There are 64 arguments
  argument1 is a.c
  argument2 is c.c
  argument3 is e.c

  % argtest `cat friend_list`
  There are 3 arguments
  argument1 is veronica
  argument2 is jhhsu
  argument3 is ghguo

(3.7.3) $* 代表所有的參數

把剛剛的 Shell Script 再加以擴充如下 :

  #!/bin/sh

  echo "There are $# arguments"
  echo "argument1 is $1"
  echo "argument2 is $2"
  echo "argument3 is $3"
  echo "all arguments : $*"

拿這個 Shell Script 來試試看 :

  % argtest a b c
  There are 3 arguments
  argument1 is a
  argument2 is b
  argument3 is c
  all arguments : a b c

  % argtest 'a b c'
  There are 1 arguments
  argument1 is a b c
  argument2 is
  argument3 is
  all arguments : a b c

  % argtest "a b c"
  There are 1 arguments
  argument1 is a b c
  argument2 is
  argument3 is
  all arguments : a b c

  % argtest *
  There are 7 arguments
  argument1 is haha
  argument2 is haha1
  argument3 is haha2
  all arguments : haha haha1 haha2 memo1 memo13 memo2 memo23

(3.7.4) 參數的移動 shift

前面提過 , $n 是第 n 個參數的內容 , 但我們要特別注意的是 , n
必需小於 10 , 也就是說 , 不可能有 $10 這樣的東西 . 那假如我
們參數的個數很多 , 超過了 10 個 , 而又想取得超過 10 個後的參
數 , 要如何做 ? 答案就在 shift .
shift 是一個單純的命令 , 它把 $n 的內容放到 $n-1 中 . 如 $2
放到 $1 , $3 放到 $2 ...... 但 $1 並不會放到 $0 , 而是永遠的
消失 . 看下面的 Shell Script 及執行結果 :

  #!/bin/sh

  echo "There are $# arguments"
  echo "all argument : $*" 
  shift
  echo "There are $# arguments"
  echo "all argument : $*" 
  shift
  echo "There are $# arguments"
  echo "all argument : $*"
  shift
  echo "There are $# arguments"
  echo "all argument : $*"

  % argtest a b c d e f g h i j

  There are 10 arguments
  all argument : a b c d e f g h i j
  There are 9 arguments
  all argument : b c d e f g h i j
  There are 8 arguments
  all argument : c d e f g h i j
  There are 7 arguments
  all argument : d e f g h i j

所以從上面的例子得知 , 每 shift 一次 , 我們就可以在 $9 的地方
得到原來的第 10 個參數內容 . 如此一來 , 要取得第 10 個以後的參
數就一點也不困難了 . 還有 , $1 雖然會在 shift 後被丟掉 , 但假
如 $1 以後還要用到的話 , 我們仍然可以在 shift 前將它保留起來 :
    .
    .
    .
  temp=$1
  shift
    .
    .
    .                
                



Chap 4 -- Shell Programming 中的語法


在 Chap 3 學完之後 , 相信各位已經可以寫一些小的程式 . 但是總
好像少了些什麼 ? 不錯 , 這些可說是每種 Programming Language
中的靈魂 : 語法 ! 語法包含了條件邏輯的判斷 , 迴圈 , 函數呼叫
等 ......有了這些 , Shell Programming Language 才算完整 .


(4.1) if 結構

  if [ 測試條件 ]        
  then
     .
     .
  fi
 
我們可以很輕易的看到 , if 與 fi 之間就是典型的 if 結構
假如測試條件成立 , 就作 then 與 fi之間所夾的事情 .
還有其它許許多多的流程控制都要用到 "測試條件" , 在繼續
介紹這些流程控制之前 , 我們得先介紹測試條件 .


(4.2) 測試條件

測試條件大體上可分為 (I) 字串測試  (II) 整數測試
                     (III) 檔案測試 

(I) 字串測試 :

  string1 = string2       #  string1 等於 string2
  string1 != string2      #  string1 不等於 string2
  string                  #  string is not null
  -n string               #  string is not null
  -z string               #  string is null
   
  前面提過 , 變數設定可能像這種樣子 :  name=veronica
  那假如我們要把 name 與某一字串做相等比較 , 應該這樣做 :

    "$name" = veronica
   
  假如要作不相等比較 , 應該這樣做 :

    "$name" != veronica
 
  看下面的 Shell Script :

    #!/bin/sh

    name=veronica

    if [ "$name" = veronica ]
    then
      echo 'equal'
    fi

    if [ "$name" != veronica ]
    then
      echo 'not equal'
    fi

  執行結果 :

    % strcmp
    equal

  分析上面的 Shell Script , 首先 , 把 veronica 指定給 name
  此時 $name 就是 veronica . 執行到 if [ "$name" = veronica ]
  "$name" 就會換成 veronica , 所以變成 :

    if [ veronica = veronica ]     <-- 字串相等比較成立

  因為比較成立了 , 所以就做 then 至 fi 之中的事 , 也就是顯示
  equal .
 
  接著執行 if [ "$name" != veronica ] , 再一次的 , "$name"
  被換成 veronica , 所以 : if [ veronica != veronica ]
  測試條件不成立 . not equal 將不會被顯示出來 .
     
  至於 string , -z string , -n string 則可以測試是否為空字串
  看下面的 Shell Script :

    #!/bin/sh

    myaddr=                        # myaddr is null   
    youaddr=taipei                 # youraddr is not null

    if [ -z "$myaddr" ]            # 測試條件成立
    then
      echo 'my string is null'
    fi

    if [ -n "$myaddr" ]            # 測試條件不成立
    then
      echo 'my string is not null'
    fi

    if [ -z "$youaddr" ]           # 測試條件不成立
    then
      echo 'your string is null'
    fi

    if [ -n "$youaddr" ]           # 測試條件成立
    then
      echo 'your string is not null'
    fi

  執行結果 :

    % isstrnull
    my string is null
    your string is not null
  

(II) 整數測試

  我們曾提過 , 變數並沒有所謂的資料型別 , 那為什麼會有整數
  測試呢 ? 這是因為 , 整數測試中 , 變數的內容被當成整數來看
  待 . 如 : money=30 , 一般情況下 , money 是放著 30 這個字串
  但在整數測試中 , money 被視為整數的 30 .

  整數的測試如下 :

  int1 -eq int2            #  int1 = int2    等於  
  int1 -ge int2            #  int1 >= int2   大於等於
  int1 -gt int2            #  int1 > int2    大於
  int1 -le int2            #  int1 <= int2   小於等於
  int1 -lt int2            #  int1 < int2    小於
  int1 -ne int2            #  int1 != int2   不等於
 
  我們舉一個例子 :

    #!/bin/sh

    if [ "$#" -eq 2 ]              # 假如參數個數等於 2  
    then
      echo 'The number of arguments is two'
    fi

  執行結果 :

    % digi
    % digi a b
    The number of arguments is two
    % digi a 
    % digi "abc" 'def'
    The number of arguments is two
  
  整數的比較常用在整個 Shell Script 的開頭 , 尤其是那些須要
  參數的 Shell Script . 就以前 plus 這個 Shell Script 而言
  plus 需要兩個參數 , 那我們便可以在 Shell Script 的一開頭
  就檢查使用者所給的參數個數是不是兩個 , 假如是 , 才做兩個
  參數相加的動作 , 若參數個數不等於兩個 , 就顯示訊息給使用
  者 , 並且脫離 , 不繼續做 , 整個 Shell Script 如下 :

    #!/bin/sh

    if [ "$#" -ne 2 ]               # 假如參數個數不等於 2    
    then
      echo 'plus needs two arguments'
      exit                          # 脫離整個 Shell Script
    fi

    echo `expr $1 + $2`             # 顯示參數 1 加參數 2
                                    # 的值

  執行結果 :
  
    % plus2
    plus needs two arguments
    % plus2 3 5
    8


(III) 檔案測試

  我們在 Shell Script 中也可以做檔案測試 :

  -d file              # 檔案是否為一個目錄
  -f file              # 檔案是否為一個普通檔案
  -r file              # 檔案是否為可讀
  -s file              # 檔案長度是否不是 0
  -w file              # 檔案是否為可寫入
  -x file              # 檔案是否為可執行
 
  若我們要測試 /etc/passwd 是否存在 , 並且是普通檔案的話 :

    if [ -f /etc/passwd ]     # 因為 /etc/passwd 存在 , 而且
                              # 它是一個普通檔案 , 所以測試
                              # 會成立

    if [ -x /etc/passwd ]     # 因為 /etc/passwd 不是可執行
                              # 檔 , 所以測試不會成立


(4.3) 命令測試

實際上 , 命令也可以當作測試的條件 , 這是因為 , 我們每執行
完一個命令 , 總會有傳回值 , 這個傳回值假如是 0 , 則代表
命令執行成功 , 假如為非 0 的值 , 則代表命令執行失敗 .
看看下面的 Shell Script :

  #!/bin/sh

  if w | grep "^jhhsu" >&-
  then
    echo $?
  fi

  if w | grep "^veronica" > /dev/null
  then
    echo 'veronica is online'
  fi

  這個 Shell Script 值得好好研究 , 首先 , $? 代表上個命令的
  傳回值 . 假如在這個系統內 jhhsu 正在線上 , 而 veronica 並
  不在線上 . 那兩次 echo $? 的結果將會是 0 與 1 , 分別代表
  成功與不成功 . 還有 , 假如 if 所測試的是命令 , 那並不需要
  [  ] 號 . 前面為了方便 , 以及看起來有結構化 , 我把 if 的結構
  寫成 :


    if [ 測試條件 ]
    then
       .
       .
    fi

 
  但實際上 , 原來的結構應該是這種樣子 :


    if test_command
    then
       .
       .
    fi

 
  也就是說 , if 後接的必須是一個測試命令 , 而不是 [ 測試條件 ]
  所以 , 我們得用 test 這個命令來執行我們的測試條件 . 就像下面

    if test "$1" -gt 10
   
  但是 , test 測試條件 也可直接換成 [ 測試條件 ] , 所以我就寫
  成 if [ 測試條件 ]
  下面左右兩邊的 Shell Script 是完全相等的 :

                                  |
    #!/bin/sh                     |   #!/bin/sh
                                  |
    if [ "$#" -ne 2 ]             |   if test "$#" -ne 2
    then                          |   then
      echo 'needs two arguments'  |     echo 'needs two arguments'
      exit                        |     exit
    fi                            |   fi
                                  |             
    echo `expr $1 + $2`           |   echo `expr $1 + $2`
                                  |

  >&- 與 /dev/null 可參考 (2.5.2) , grep 可參考附錄二


(4.4) 組合測試條件的邏輯運算子

各位在看完 (4.3) 後 , 若你原本對管線 , 導向等等的課題有一定
的認識 , 那你已經可以寫出頗為有用的 Shell Script 了 .
在 (4.4) , 我們還要介紹三種邏輯運算子 not , and 與 or

(I) ! 代表反面的邏輯 : not

  也許你已經發現了 , 在 (4.2) 中 , 不管是字串的測試或者是整數
  的測試 , 都有反面的邏輯存在 :

    string1 = string2         # 字串1 與字串2 相等 
    string1 != string2        # 字串1 與字串2 不相等

    int1 -gt int2             # 整數1 大於 整數2
    int1 -le int2             # 整數1 小於等於 整數2

  但在檔案的測試方面 , 卻沒有反面的邏輯 . 此時 , 我們就要靠
  ! 來達成反面的測試 , 如 :

    if [ -f /etc/passwd ]     # /etc/passwd 是普通檔案嗎 ?
    if [ ! -f /etc/passwd ]   # /etc/passwd 不是普通檔案嗎 ?

  當然 , 在字串或整數比較方面 , 你也可以不用原來就已經有的運
  算子 , 而改用 ! 以代表反面的邏輯 . 請看下面的 Shell Script

    #!/bin/sh

    if [ ! "$1" -gt 10 ]      # 假如第一個參數的值不大於 10
    then
      echo 'argument is less than or equal to 10'
    fi

  執行結果 :

    % unary 3
    argument is less than or equal to 10
    % unary 11
  
(II) -a 代表邏輯上的 and

  講到目前為止 , 我們的比較都僅限於一個表示式 , 像某一變數的
  內容是否等於一個字串 , 或是某一變數的內容是否大於一個整數
  那假如我們想做如此的測試 : " 一個數字是否大於 10 而且小於
  100 , 也就是介於 10 到 100 之間 " 那我們就得用 -a 來達成 .
  請看下面的 Shell Script :
 
    #!/bin/sh

    if [ "$1" -gt 10 -a "$1" -lt 100 ]  # 10 < 參數一 < 100 ?
    then
      echo '10 < argument < 100'
    fi

  我們可以把上面 [  ] 中的測試條件看成 :

    第一個參數大於10 且 第一個參數小於100 
   
  有些人會想 : 我是不是應該用括號來確保運算優先權的順序 , 其實
  可以不必 , 因為 -a 比 -gt 或 -lt 的優先權低 , 也就是說 ,
  "$1" -gt 10 與 "$1" -lt 100 會分別先運算完後才做 and 的動作 .
  假如你有"優良"的寫程式習慣 , 那你仍然可以加上括號 , 只是 ,
  要以反斜線來去除括號的特殊意義 , 如下 :

    if [ \( "$1" -gt 10 \) -a \( "$1" -lt 100 \) ]

(III) -o 代表邏輯上的 or

  與 and 不同的 , or 只要有一項成立就可以了 . 比如說 :

    if [ "$1" -le 10 -o "$1" -ge 90 ]

  表示測試第一個參數是否符合小於等於10 或 符合大於 90 其中之一
  的敘述 . 只要符合其中任一句敘述 , 條件就成立 . 

  在比較複雜的邏輯中 , or 與 and 有同時存在的可能 . 此時 , 到底
  誰的優先權比較高呢 ? 答案是 and , 舉個例子如下 :

    if [ "$1" -le 10 -o "$1" -gt 50 -a "$1" -lt 100 ]

                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                這部份先做

         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  再做這部份
           
  假如你想改變 and 與 or 之間的優先權 , 請使用括號 .


(4.5) 更多的 if 流程控制

當我們學完測試條件之後 , 流程控制就很簡單了 , 在 (4.1)
中 , 我們已經看過了 if 的結構 , 現在我們來看看其它的結構
所有的 if 結構都以 fi 做結尾 , 各位可以把 if 至 fi 看成
一個區塊 .


(I) if test_command
    then
       .
       .
    fi

    這個結構 (4.1) 已提過 , 不再贅述
     
(II) if test_command
     then
        .
        .
     else
        .
        .
     fi

    這個結構很簡單 , 假如 test_command 成立 , 那就做 then
    到 else 之間的事 ; 假如 test_command 不成立 , 那就做
    else 到 fi 之間的事 . 下面是一個簡單的例子 .

      #!/bin/sh

      if [ "$1" -ge 0 ]
      then        
        echo 'positive'
      else        
        echo 'negative'
      fi

    執行結果 :

      % mytest 10               # 10 大於 0
      positive
      % mytest -5               # -5 小於 0
      negative


(III) if test_command1
      then
         .
         .
      elif test_command2
      then
         .
         .
         .
      elif test_commandn
      then
         .
         .
      else
         .
         .
      fi    

     上面的結構也很簡單 , 假如 test_command1 成立 , 就做
     then 與 elif test_command2 之間的事 . 假如 test_command1
     不成立 , 就測試 test_command2 看成不成立 , 假如不成立
     就繼續測試下去 . 假如所有的測試都不成立 , 就會執行 else
     與 fi 之間的事 . 下面是一個典型的 Shell Script :

       #!/bin/sh

       time=`date | cut -c12-13`
      
       if [ "$time" -gt 0 -a "$time" -lt 12 ]
       then
         echo 'Good Morning'
       elif [ "$time" -ge 12 -a "$time" -lt 17 ]
       then
         echo 'Good Afternoon'
       else
         echo 'Good Evening'
       fi

     首先 , date | cut -c12-13 先執行 , date 輸出目前時間 ,
     但我們並不要全部的輸出 , 只要第12及第13個字元 , 也就是
     目前是幾點 , 所以我們把 date 的輸出以管線交給 cut 這個
     命令去剪下第12及第13個字元 , 然後把這兩個字元設給 time
     接著進入條件測試 . 一開始先測試是否零點到十二點之間 ,
     假如是 , 就顯示 Good Morning , 並脫離 if 結構 . 假如不
     在零點到十二點之間 , 那就看看是不是在十二點到十七點之間
     假如是 , 就顯示 Good Afternoon , 並脫離 if 結構 , 假如
     又不是在十二點到十七點之間 , 就顯示 Good Evening

     
(4.6) case 結構


     case value in
     pattern_1)  .
                 .
                 .;;
     pattern_2)
                 .
                 .
                 .;;
      ...

     pattern_n)  .
                 .
                 .;;
     esac

   
case 結構是以 case 為開頭 , 以 esac 為結束 . 其中 , 我們
能自由指定的 , 包含 value , pattern 以及若符合 pattern 則
作那些事 . case 的工作原理很簡單 , 它把 value 的值與下面的
pattern 做比較 , 假如遇到符合的 , 就做 pattern 之後的內容
直到遇見一個 ;; 號為止 . 各位一定要注意 , 每個 pattern 結束
都一定要有一個 ;; 號 , 否則會有錯誤 .
value 就是一個變數 , pattern 是一個符合的樣版 . 先看一個
Shell Script , 這個 Shell Script 從參數得到一個數字 , 並把
這個數字轉成相對應的英文 :

 
  #!/bin/sh

  if [ "$#" -ne 1 ]
  then
    echo 'Usage : transnum '
    exit
  fi

  case "$1" in

  0) echo 'zero';;
  1) echo 'one';;
  2) echo 'two';;
  3) echo 'three';;
  4) echo 'four';;
  5) echo 'five';;
  6) echo 'six';;
  7) echo 'seven';;
  8) echo 'eight';;
  9) echo 'nine';;
  *) echo 'must a single digit';;

  esac  


執行結果 :

  % transnum 4
  four
  % transnum 18
  must a single digit
  % transnum veronica
  must a single digit

在上面的 Shell Script 中 , value 是第一個參數 , 而 pattern
的值分別為 0,1,2,3,4,5,6,7,8,9,* . 所以 , 當執行到 case 這
個結構時 , 第一個參數就與下面一個個 pattern 做比較 , 直到
遇見符合的 pattern , 就做 pattern 後面的事 . 比較特殊的是 ,
pattern 若是 * 號 , 則表示都不符合所要做的事 . 像上面 , 我
們只接受一位數字 , 假如你給與的是兩位數字以上 , 或是字串 ;
既然都不符合 0 到 9 的 pattern , 那就會顯示一段錯誤訊息 .
還有 , pattern 可以比較複雜 , 如下面的格式都是可以的 :

  [a-z]         <-- 符合小寫字元
  [0-9]         <-- 符合 0,1,2,3,4,5,6,7,8,9
  ?             <-- 符合任何一個字元

你也可以把兩個 pattern 以 | 做邏輯的 or , 看下面的例子


  #!/bin/sh

  if [ "$#" -ne 1 ]
  then
    echo 'need a animal name'
  fi

  case "$1" in

  ant | cat ) echo 'I hate this';;
  bird | dog ) echo 'I like this';;

  esac


執行結果 :

  % testani ant
  I hate this
  % testani bird
  I like this
  % testani cat
  I hate this
  % testani dog
  I like this

 
(4.7) while 結構


  while test_command
  do
       .
       .
  done
 

while 結構相當簡單 , 它可說是一種迴圈的型式 , 只要 test_command
成立 , 就會一直做 do 與 done 之間所夾的事 . 下面的 Shell Script
會顯示 0 到 100


  #!/bin/sh

  var=0                               # 把 var 的初始值設為 0

  while [ "$var" -le 100 ]            # 當 $var 小於等於 100
  do
    echo -n "$var"                    # echo -n 為顯示但不換行
    var=`expr $var + 1`               # 把 var 的值加 1
  done


假如我們想無條件作無窮迴圈 , 那你可以看看下面的 Shell Script ,
它會在終端機上從 0 開始印 , 直到你按下 Ctrl-c 為止


  #!/bin/sh

  var=0

  while test 0               # 注意 , test_command 為 test 0        
  do
    echo -n "$var"
    var=`expr $var + 1`
  done


本來在我們的想法中 , test 後應該是接一個測試條件 , 就像 :

  test "$var" -lt 100

test 0 是什麼意思 ? 其實很簡單 , 測試條件假如成立 , 那就
會是 0 , 假如不成立 , 那就會是 1 . 所以 , 像 $var 若小於 100
那麼 , test "$var" -lt 100  就會被換成  test 0  .
所以 , 我們所看到的 :

  while test 0           <-- 恆成立 , 所以為一無窮迴圈
  do
       .
       .
  done
 

(4.8) until 結構


  until test_command
  do
       .
       .
  done


與 while 不同的 , until 後面接的 test_command 要不成立才會
一直做 do 與 done 之間夾的事 , 一但 test_command 成立了 ,
整個 until 結構就結束 .
舉上面的顯示 0 到 100 的例子 , 假如我們要把原來的 while 結構
改成 until 結構 , 那我們的想法必然是 : 一直加 1 , 直到大於
100 為止 . 那整個 Shell Script 如下 :


  #!/bin/sh

  var=0

  until [ "$var" -gt 100 ]      # 直到大於 100 為止  
  do
    echo -n "$var"
    var=`expr $var + 1`
  done


讓我們好好比較 while 與 until :


                                |
  #!/bin/sh                     |   #!/bin/sh
                                |
  var=0                         |   var=0  
                                |
  while [ "$var" -le 100 ]      |   until [ "$var" -gt 100 ]
  do                            |   do
    echo -n "$var"              |     echo -n "$var" 
    var=`expr $var + 1`         |     var=`expr $var + 1`
  done                          |   done
                                |


做同樣一件事 , 但是它們的測試條件卻剛好相反 . while 是小於等於
100 , 而 until 卻是大於 100 . 從這裡 , 我們看到了 while 與
until 的互補性 .
until 結構有用的地方是在於 : 它可以用來等待某事件的發生 , 看下
面的 Shell Script :


  #!/bin/sh

  if [ "$#" -ne 1 ]                     # 假如參數個數不等於 1
  then
    echo 'Usage: scanuser username'
    exit
  fi

  until who | grep "^$1" > /dev/null
  do
    sleep 60
  done

  echo "$1 has logged on"


上面是一個頗為有用的 Shell Script , 我相信各位可以看得懂前幾列
特別要解釋的是 until 的結構 . 在這個 Shell Script 中 , 我們
想做的事是 : 每 60 秒檢查一次 , 看看某 user 是不是在系統中 .

  until who | grep "^$1" > /dev/null

上面這列看起來很複雜 , 其實只要你熟悉命令及導向,管線 , 那實在
沒有什麼神秘的 . 我們知道 , until test_command 是只要
test_command 不成立 , 就一直做 do 與 done 之間的事 .

  who | grep "^$1" > /dev/null

把 who 的輸出交給 grep 去抓出看看有沒有第一個參數的人名 , 並
把應該正常輸出的訊息丟給 /dev/null 這個垃圾筒 .
假如沒有第一個參數的人名 , 就執行 sleep 60  ( sleep 60 是暫
停 60 秒的意思 ) , 假如有第一個參數的人名 , 就會脫離 until
結構 , 並且顯示那個人已經簽入系統 .
我們可以把這個 Shell Script 以背景執行 , 你會發覺它真的有用 .


(4.9) for 結構


  for var in word1 word2 ..... wordn
  do
       .
       .
  done


請各位注意 , Shell Programming 中的 for 與一般程式語言的 for
有很大的不同 . 我們能指定的包含 var , word1 , word2 ......
( word1 word2 .... wordn 為了方便 , 我把它稱為 word list )
以及 do 與 done 之間所做的事 . 我們還是先看一個例子 :


  #!/bin/sh

  for i in 1 2 peter bob
  do
    echo "$i"
  done


執行結果 :

  % fortest
  1
  2
  peter
  bob


我們所看到的是 , for 每執行一次 , 就把後面的資料指定給 var
像第一次執行時 , 1 被指定給 i , 第二次執行時 , 2 被指定給 i
第三次執行時 , peter 被指定給 i , 第四次執行時 , bob 被指定
給 i . 好了 , 現在我們把焦點放在 word1 word2 ..... wordn 上
它們是不是有什麼變化呢 ? 在這裡 , 我要提出幾種 :


(I) 使用命令製作出 word list

各位還記得 (3.5.3) 中 mail 給很多人的例子嗎 ?

  mail `cat member` < letter

我們知道 , `cat member` 會被換成像 jhhsu veronica peter bob
的型式 , 那不是剛好符合我們的 word list 嗎 ? 因此 , 我把
它以 for 來改寫 :

 
  #!/bin/sh

  for person in `cat member`
  do
    mail $person < letter
  done


所以各位可以看到 , 只要命令執行結果會產生 word list , 那怕
是只有一個 word 也好 , 我們都可以把它放在 in 的後面 , 並以
反單引號括起來 . 在這部份 , 運用的巧妙就得看你對命令的熟悉
程度而定了 , 並且可以再用管線組合出更複雜的命令 .
 

(II) 把參數當成 word list

在這裡要介紹一個新的符號 $@ , 它與 $* 類似 , 都是代表所有
的參數 . 不同的是 , $@ 會把每個參數加上雙引號 . 所以 , 我
們可以想成 : $* 代表 $1 $2 $3 .... 而 $@ 代表 "$1" "$2" "$3"
比較下面兩個 Shell Script :

                           |
  #!/bin/sh                |        #!/bin/sh 
                           |
  for arg in $*            |        for arg in $@
  do                       |        do
    echo $arg              |          echo $arg
  done                     |        done
                           |
                           |
執行結果 :                 |  執行結果 :
                           |
  % arg1 'a b' c           |    % arg2 'a b' c
  a                        |    a b
  b                        |    c
  c                        |


下面還是以寄信做例子 , 但此時 , 收信人我們不寫在檔案中 , 而
直接由命令列中的參數指定 :

  #!/bin/sh

  for person in $@
  do
    mail $person < letter
  done




Chap 5 -- 讀取資料 , 呼叫函數 , 偵錯

(5.1) read 用來讀取資料

截至目前為止 , 我們變數的來源有兩個地方 , 一是來自參數 , 一是
來自直接在 Shell Script 中直接指定 . 那變數能不能在 Shell
Script 執行時才讀取呢 ? 此時就要用 read . read 的格式為 :

  read variable

它會把讀取到的資料放到 variable 中 . 舉一個小小的例子 :


  #!/bin/sh

  echo -n 'Input number1 --> '
  read number1
  echo -n 'Input number2 --> '
  read number2

  echo `expr $number1 + $number2`


執行結果 :


  % plus3
  Input number1 --> 5
  Input number2 --> 10
  15


上面這個 Shell Script 可以由使用者任意輸入兩個變數 , read
分別把它們放到 number1 及 number2 , 最後再把 number1 及
number2 加起來 .
我再舉一個例子 , 你可以用 read 來製做出類似選單的東西 :

     
  #!/bin/sh

  cat << End_of_menu

  ------------------------
  |                      |
  | You have 4 choice :  |
  |                      |
  | 1. plus  +           |
  |                      |
  | 2. minus  -          |
  |                      |
  | 3. multiple  *       |
  |                      |
  | 4. divide  /         |
  |                      |
  ------------------------

  End_of_menu

  echo -n 'Input your choice --> '
  read choice
  echo -n 'Input number1 -->'
  read number1
  echo -n 'Input number2 -->'
  read number2

  case $choice in
  1) echo `expr $number1 + $number2`;;
  2) echo `expr $number1 - $number2`;;
  3) echo `expr $number1 \* $number2`;;
  4) echo `expr $number1 / $number2`;;
  esac
 

上面是一個選單式的計算機 , 它可以選擇你要作的運算 , 並讀取
兩個數字來當運算元 . 這個 Shell Script 融合了我們在 (2.5.2)
中的 cat << c 的觀念 , 特別要注意的是 , 在上面的 Shell Script
中 , End_of_menu 一定要獨立一列 , 而且一定要在那列的開頭 .
接著 , read choice 把我們要做的四則運算種類 ( 1 代表加 , 2
代表減 , 3 代表乘 , 4 代表除 ) 放在 choice 中 . 再來 , 讀入
兩個數字來當運算元 . 最後 , 進入一個 case 的結構 . 在 case
中根據 choice 的值來做不同的運算 .


(5.2) 呼叫函數

在程式寫作的型態上 , 結構性的程式的確比雜亂無章的程式要好 .
因此 , 使用函數變成了一項不可或缺的技巧 , 而且可以把整個
程式拆成數個部份分別寫作 . 在 Bourne Shell Programming
中 , 我們也可以使用函數 , 函數的格式如下 :


  function_name()
  {
    command
    command
      .
      .
  }


我們把上面加減乘除的例子以函數的形式再寫一遍 :

  #!/bin/sh

  plus()
  {
    echo 'Plus Function'
    echo `expr $number1 + $number2`
  }

  minus()
  {
    echo 'Minus Function'
    echo `expr $number1 - $number2`
  }

  multi()
  {
    echo 'Multiple Function'
    echo `expr $number1 \* $number2`
  }

  div()
  {
    echo 'Divide Function'
    echo `expr $number1 / $number2`
  }

  main_select()
  {
    cat << '  End_of_menu'

    ------------------------
    |                      |
    | You have 4 choice :  |
    |                      |
    | 1. plus  +           |
    |                      |
    | 2. minus  -          |
    |                      |
    | 3. multiple  *       |
    |                      |
    | 4. divide  /         |
    |                      |
    ------------------------

    End_of_menu


    echo -n 'Input your choice --> '
    read choice
    echo -n 'Input number1 -->'
    read number1
    echo -n 'Input number2 -->'
    read number2

    case $choice in
    1)plus;;
    2)minus;;
    3)multi;;
    4)div;;
    *)echo 'Invalid choice'
      exit;;
    esac
  }

  main_select                       # 程式從這裡開始


使用這種函數寫作有幾點要注意 :

(I) 下面的情形 , 左邊可以正確的執行 , 右邊卻不行

    a_func()                |      a_func 
    {                       |
      b_func                |      a_func()
    }                       |      {
                            |        b_func
    b_func()                |      }
    {                       |
                            |      b_func()
    }                       |      {
                            |
    a_func                  |      }


(II) 命令列下參數的傳遞必須先把這些參數指定給另一個變數
     然後才能正確的工作 . 看下面的例子 :

     #!/bin/sh

     main()
    {
      echo $1                # error ?
      echo $2                # error ?
    }

    main

    上面的例子看起來沒錯 , 但實際上並不能 work , 你可能要
    改成下面的樣子 :

    #!/bin/sh

    main()
    {
      echo $number1
      echo $number2
    }

    number1=$1
    number2=$2
    main
   

(5.3) Shell Script 的偵錯

使用 sh -x shell_script_name  可以對一個 Shell Script 做
偵錯的動作 . 假如你的 Shell Script 不能正常的執行 , 或者是
執行的結果不符合你所預料的 . 出錯的地方可能為下列幾項之一 :

(I) 請檢查 Shell Script 是否有可讀及可執行權限 .

(II) 請注意 , Shell Programming Language 不是完全自由語法
     就像 if [ "$#" -ne 1 ]  就不能寫成 if ["$#" -ne 1]
     也不能寫成 if[ "$#" -ne 1 ] , 這些要特別注意 . 假如
     執行時有錯誤訊息 , 你要詳加檢查程式的語法結構 . 該
     空格的地方要有空格 .

(III) 要了解 , $variable 才是變數的內容 , 你很有可能把
      variable 當成變數的內容 .

(IV) 還有 , 你可能拼錯字了 , 這是很常見的 .

(V) 有些情況下 , 變數中的內容並不是你預期的 , 以致造成
    錯誤 . 此時你可以在程式中可疑的地方適時的加上一些
    偵錯點 , 把這些變數內容顯示出來看看 .

(V) 最後 , 邏輯上的錯誤是很難找到的 , 你可能也要注意 .



附錄一 : Shell Summary
-------------------------------------------------------------------


## 變數的指定 :

variable=value             

此時 , variable 是被當成字串看待 .

如 : NNTPSERVER=news.csie.nctu.edu.tw

     其中 , NNTPSERVER 是 variable ; news.csie.nctu.edu.tw 是 value


## 變數的內容 :

$variable


## 對 shell script 作偵錯執行的動作 :

sh -x [your_shell_script]

如 : sh -x search_string

     其中 , search_string 這個 shell script
     必需有 read 及 execute 的權限


## shell script 中的特殊符號 :

$# 參數個數
$n 第 n 個參數
$* 所有的參數
$@ 與 $* 一樣 , 除了每個參數都加上 "  "
$? 上一個命令傳回的值
$$ 目前此 shell 的 pid   // 當你在 script 中要 creat 一個檔 , 但不希望
                         // 這個檔案名是固定時 ( 尤其是有兩個人同時使用
                         // 這個 shell script ) 總不能 creat 的檔名取成
                         // 一樣 , 這時候 , $$ 就可派上用場


## 對整數作比較的運算子 :

int1 -eq int2            //    int1 = int2
int1 -ge int2            //    int1 >= int2
int1 -gt int2            //    int1 > int2
int1 -le int2            //    int1 <= int2
int1 -lt int2            //    int1 < int2
int1 -ne int2            //    int1 != int2


## 對字串作比較的運算子 :

string1 =string2
string1 !=string2
string               // string is not null
-n string            // string is not null
-z string            // string is null


## 對檔案作測試的運算子 :

-d file              // if file is a directory
-f file              // if file is a ordinary file
-r file              // if file is readonly
-s file              // if file has nonzore length
-w file              // if file is writeable
-x file              // if file is excuteable


## 邏輯運算子 :

!    // negation
-a   // logic AND
-o   // logic OR


## 讀取使用者輸入的資料 :

read variable


## 迴圈 , 反覆執行 :


(1) while :
   
    while test_command
    do
      command
      command
        .
        .
    done


(2) until :

    until test_command
    do
      command
      command
        .
        .
    done 

        
(3) for :

    for var in word1 word2 ..... wordn
    do
      command
      command
        .
        .
    done


## 選擇性條件 :


(1) case :

    case value in
    pattern_1) command
               command
                 .
                 .
               command;;
    pattern_2) command
                 .
                 .
               command;;
      ...

    pattern_n) command
                 .
                 .
               command;;
    esac       


(2) if :

    型式一 :
   

    if test_command
    then
      command
      command
        .
        .
    fi         


    型式二 :


    if test_command
    then
      command
      command
        .
        .
    else
      command
      command
    fi
   

    型式三 :


    if test_command1
    then
      command
        .
        .
    elif test_command2
    then
      command
        .
        .
    elif test_command3
    then
      command
        .
        .
    else
      command
        .
        .
    fi 
 

## special two command logic :

cmd1 && cmd2    // cmd2 is excuted when cmd1 is true (return 0)
cmd1 || cmd2    // cmd2 is excuted when cmd1 is false



Example :

---------------------- 第一個參數與字串比較 -----------------

#!/bin/sh

echo $1

if [ "$1" = 'y' ]
then
  echo 'Yeah! You are right!'
fi

if [ "$1" = 'n' ]
then
  echo 'Ooop! You are wrong!'
fi


執行結果 :

%scom y
y
Yeah! You are right!
%scom n
n
Ooop! You are wrong!


---------------------- Del_From_Inode -----------------------


#!/bin/sh

if [ "$#" -ne 1 ]
then
  echo 'Usage : del_from_inode '
  exit 1
fi

find . -inum "$1" -ok rm '{}' \;


---------------------- 字串比較的例子 -----------------------


#!/bin/sh

if [ "$#" -lt 1 ]
then
  echo 'again!'
  exit 1
fi

if [ "$1" = 'lala' ]
then
  echo 'lala is haha'
else
  echo 'unknown!!!'
fi


執行結果 :

%para3
again!
%para3 lala
lala is haha
%para3 dada
unknown!!!


--------------- 計算某一目錄底下有多少檔案或目錄 ------------

#!/bin/sh

if [ "$#" -ne 2 ]
then
  echo 'Usage : count_report '
  exit
fi

case "$2" in

f)

  file=0
  for f in `find $1 -depth 1 -type f`
  do
    file=`expr ${file} + 1`
  done
  echo "Total files is ${file}"
  ;;


d)

  directory=0
  for f in `find $1 -depth 1 -type d`
  do
    directory=`expr ${directory} + 1`
  done
  directory=`expr ${directory} - 1`
  echo "Total directories is ${directory}"
  ;;

esac

---------- 一個比較長的例子 ( 可執行但功能並不完整 :p ) -----------

#!/bin/sh

COMPILER=cc

pause_until_enter()
{
  echo
  echo 'Press enter to continue ......'
  read enter_key
}

exit_shell_script()
{
  sleep 1
  clear
  exit
}

error_handle()
{
  if [ $? -ne 0 ]
  then
    echo "***** Some error occur , Please check ~/$$.err *****"
    echo -n "Display ~/$$.err now ? (y/n) --> "
    read error_select
    if [ $error_select = 'y' ]
    then
      cat ~/$$.err | more
      pause_until_enter
      case $main_select in
      1) modify_now;;
      esac
    fi
  fi


modify_now()
{
  echo -n 'Modify your source code now ? (y/n) --> '
  read modify_now
  if [ $modify_now = 'y' ]
  then
    $EDITOR $source_code_name
  fi
}

read_source_code_name()
{
  echo -n 'Enter source codes name --> '
  read source_code_name
}

read_out_exe_file_name()
{
  echo -n 'Input executable filename , Press enter for a.out --> '
  read out_exe_file_name

  if [ "$out_exe_file_name" = ''  ]
  then
    echo 'Use default name : a.out'
  fi
}

read_library_name()
{
  echo -n 'Enter your library name --> '
  read library_name
}

read_obj_name()
{
  echo -n 'Enter your obj name --> '
  read obj_name
}

build_exe()
{
  read_source_code_name
  read_out_exe_file_name
  echo -n 'Link library? (y/n) --> '
  read link_lib_choice
 
  if [ "$link_lib_choice" = 'y' ]
  then
    echo -n 'Enter library name --> '
    read lib_name
  else
    lib_name=
  fi
 
  echo -n 'Creat symbol table for debugger? (y/n) --> '
  read debug_choice

  if [ "$debug_choice" = 'y' ]
  then
    $COMPILER -g -o ${outfile}.out $source_code_name $lib_name > ~/$$.err 2>&1
    error_handle
  else
    $COMPILER -o ${outfile}.out $source_code_name $lib_name > ~/$$.err 2>&1
    error_handle
  fi


compile_only()
{
  read_source_code_name
  $COMPILER -c $source_code_name


list_lib_content()
{
  read_library_name
  ar t $library_name | more
  pause_until_enter
}

add_obj_into_lib()
{
  read_library_name
  read_obj_name

  if [ -f $library_name ]
  then
    ar q $library_name $obj_name
  fi


read_config()
{
  sed -n 1,1p comenv.conf
  pause_until_enter
}


main_menu()
{
  clear
  cat << '  main_menu_end'

  Main Menu
------------------------------

  1. Compile Source Code

  2. Library Maintain

  3. Execute File 

  4. Configuration

  5. Exit

  main_menu_end

  echo -n 'Enter your choice --> '
  read main_select

  case $main_select in
  0);;
  1) compile_menu;;
  2) maintain_menu;;
  3) execute_menu;;
  4) config_menu;;
  5) exit_shell_script;;
  *) main_menu;;
  esac
}

compile_menu()
{
  clear
  cat << '  compile_menu_end'

  Compile Menu
------------------------------

  1. Build Excutable File

  2. Compile Only

  3. Back To Main Menu

  4. Exit

  compile_menu_end

  echo -n 'Enter your choice --> '
  read compile_select

  case $compile_select in
  1) build_exe;;
  2) compile_only;;
  3) main_menu;;
  4) exit_shell_script;;
  *) compile_menu;;
  esac
}

maintain_menu()
{
  clear
  cat << '  maintain_menu_end'

  Library Maintain
------------------------------

  1. List library content 
 
  2. Add objfile into library 

  3. Remove objfile from library

  4. Back to Main Menu

  5. Exit

  maintain_menu_end

  echo -n 'Enter your choice --> '
  read maintain_select

  case $maintain_select in
  1) list_lib_content;;
  2) add_obj_into_lib;;
  3) remove_obj_from_lib;;
  4) main_menu;;
  5) exit_shell_script;;
  esac
}

execute_menu()
{
  echo -n 'Enter execute file name --> '
  read execute_file_name
  $execute_file_name
  pause_until_enter
}

config_menu()
{
  clear
  cat << '  config_menu_end'

  Configuration Menu
------------------------------

  1.View Configuration File
 
  2.Change Configuration

  config_menu_end

  echo -n 'Enter your choice --> '
  read config_select

  case $config_select in
  1) read_config;;
  2);;
  esac
}

while test 0
do
  main_menu
done




附錄二 : grep 簡介
-------------------------------------------------------------------

grep -- 從檔案每一列中找出特定的樣板格式

grep [options] regular_expressions [files]

  options :

  -b  印出符合的那一列是在整個檔案中的第幾個 byte .

  -c  只印出共有多少列符合 regular_expressions .

  -h  當我們從許多檔案中找尋適合的條件時 , 假如找到了 , 就會印出是在
      那個檔案中找到的 . 然而 , 假如我們加了 -h 這個 option . 那麼 ,
      輸出的結果就不會告訴我們 , 是在那個檔案中找到符合的樣板 .

  -i  忽略 regular_expressions 中大小寫的差別 .             

  -l  只印出在那個檔案中有找到 , 而不印出檔案中符合的樣版 .

  -n  印出符合的樣板是在檔案中的第幾列 .

  -s  抑制錯誤訊息 , 像不存在的檔案啦 , 或者是不能讀的檔案 .

  -v  印出不符合 regular_expressions 的那些列 .

  example :

  從 test1 這個檔案中 , 找出含有 app 的列 :
                     
     % grep 'app' test1

  從 passwd 這個檔案中找出到底有多少人使用 tcsh :

     % grep -c '/bin/tcsh' /etc/passwd

  從目前的目錄下 , 找出任一列開頭具有 #include 的檔案 :

     % grep -l '^#include' *

  從 test2 這個檔案中 , 找出那些不含 class 的列 :

     % grep -v 'class' test2

  列出不含 pattern 的檔案 :

     % grep -c pattern files | grep :0 | cut -d":" -f1 



regular expressions  正規表示式
-------------------------------------------------------------
 
. 可符合任意一個字元 ( 包含空白字元 )

[] 可符合方括弧中列舉的字元

[^ regular_expressions] 不符合 regular_expression

* 與零個或更多個 * 前的字元吻合

.* 符合零或更多個字元

^regular_expressions  符合在每一列開頭的 regular_expressions


下面是 regular expressions 的一些例子


  regular expressions     match     
---------------------------------------------------------------

  ring                    ring , spring , ringing , stringing
                          ^^^^     ^^^^   ^^^^        ^^^^ 
 
  .alk                    walk , talk ,  alk , skialk
                          ^^^^   ^^^^   ^^^^     ^^^^

[bB]ill                  bill , Bill , biller
                          ^^^^   ^^^^   ^^^^

number[5-9]              number7 , number90
                          ^^^^^^^   ^^^^^^^

[^ a-z]                  a21 , c1 , 123
                           ^^    ^   ^^^

ab*                      acc , ab , abb , abc
                          ^^^   ^^   ^^^   ^^^

ab.*                     ab , abb , abbc
                          ^^   ^^^   ^^^^

^T                       符合以 T 為開頭的每一列 ; This line
                                                    ^

參考資料 :
----------------------------------------------------------------

Shell :

1.  Title: Unix Shell Programming
    Authors: Stephen Kochan and Patrick Wood
    Publisher: Hayden
    Edition: 1990
    ISBN: 0-672-48448-X

2.  Title: The Unix C Shell Field Guide
    Authors: Gail Anderson and Paul Anderson
    Publisher: Prentice Hall
    Edition: 1986
    ISBN: 0-13-937468-X


General Unix Texts :

1.  Title: Unix Power Tools
    Authors: Jerry Peek, Tim O'Reilly and Mike Loukides (and other
    Publisher: O'Reilly / Bantam                       contributors)
    Edition: 1993
    ISBN: 0-553-35402-7


2.  Title: Unix in a Nutshell
    Authors: Daniel Gilly and O'Reilly staff
    Publisher: O'Reilly
    Edition: 2nd ed. 1992 (for System V and Solaris 2)
    ISBN: 1-56592-001-5


Programming :

1.  Title: Advanced Programming in The Unix Environment
    Author: Richard Stevens
    Publisher: Addison-Wesley
    Edition: 1992
    ISBN: 0-201-56317-7


Other :

1. UNIX FAQ



如何聯絡作者 :

  地址 : 交通大學十舍315R

  E-mail : jhhsu@csie.nctu.edu.tw
           u8217017@cc.nctu.edu.tw

假如您發現文章中有任何錯誤之處 , 請通知作者 .
還有 , 讀者的建議與批評也非常的歡迎 .                              

 发表于: 2007-01-12,修改于: 2007-01-12 13:32 已浏览1043次,有评论0条 推荐 投诉

  网友评论

  发表评论



Copyright © 2001-2010 ChinaUnix.net All Rights Reserved

感谢所有关心和支持过ChinaUnix的朋友们
页面生成时间:0.10802

京ICP证041476号