Shell_Programming

Power of Unix Shell Programming

簡介

Unix 之優缺點

學習 Unix Shell Programing 之方法

Shell Script 設計小秘訣

將文字檔設為可執行

Shell如何執行機械碼執行檔與shell script?

檢視執行軌跡 (Execution Tracing)

Script的基本結構及觀念

內建指令與外部指令

如何執行一個程式 (Script)

命令替換 (Command Substitution)

輸出指令: echo and printf

Read (從 STDIN 輸入)

算數運算 (Arithmetic Expression)

Shell 語言之主要元素

變數

使用者變數

使用者變數命名規則

唯讀的使用者變數

環境變數

POSIX 定義的內建環境變數

Env

Unset

特殊變數

POSIX Built-In Shell Special Variables

引數與位置變數

Shift

Shift 有什麼用?

I/O Redirection

Avoid File From Truncated by I/O Redirection

流程控制

測試指令的標的物 (Target of Condition Test)

test 指令

流程控制 - if

指令的串接: && 及 ||

Logical NOT AND and OR

流程控制 - If 之例

流程控制 - If 之例

流程控制 - Case

流程控制 - for 迴圈

流程控制 - for 迴圈

流程控制 - while/Until 迴圈

流程控制 - break and continue

Function ( 函式 )

Function ( 函數/函式 )

函式之執行

函式參數的傳遞

函式之回傳值

函式之範例

特殊檔案: /dev/null and /dev/tty

設定變數的預設值 (Default Values in Variable Expansion)

變數展開的POSIX擴充功能-字串與算數運算

變數展開的字串處理功能

檔案名稱的字串處理

變數展開的算數計算功能 (Arithmetic Expansion)

特殊符號 (Special Characters)

特殊符號的Escape (Remove Meaning of Special Characters)

雙引號(" ")之使用

選項的處理 (Option Processing)

選項的處理 -getopts

群組指令 (Command Group)

怠工指令 (Null Command)

程式的除錯工具:set

指令 eval

複雜的I/O轉向

STDIN 轉向 及 Here

Signal and Trap

nohup 程序的免死金牌與強迫終止

Links to other sources


Untitled Document
Power of Unix Shell Programming
     

      Automate Your Unix Tasks      

 

 
by Arnold Robbins & Nelson Beebe in Classic Shell Programming
Wed Dec 3 11:37:07 CST 2025 Untitled Document
簡介
Shell Script 是一種批次檔
   
當你在電腦上從事一些例行的重覆性工作,最希望能寫成批次檔,讓批次檔 自動執行,可省下很多人力。早在早期的讀卡機時代,IBM 電腦就有 JCL (Job Control Language) 讓電腦操作員,控制電腦任務的自動執行, 雖然功能很陽春,但不失為一個批次執行的雛形。微軟的 DOS, 也有執行批次檔的能力,以 .bat 為延伸檔名的檔案就是批次執行檔。 在Unix 中,Shell 不但可以跟使用者互動,也可將所要執行的指令放在檔案內,讓 Shell 當作批次執行檔執行。就程式語言的觀點而言, 其彈性能力(programming capability)已經達到一個陽春的高階程式語言。 批次檔在Unix中稱為Shell Script。 其基本步驟很簡單:
  1. 將欲執行的指令存入一個檔案
  2. 將檔案權限改成可執行
  3. 呼叫檔案執行之
Shell 的特性
   
Unix Shell 是一個命令列解釋器 (command-line interpreter),可作為使用者與 Unix 作業系統核心(kernel) 之間的介面。其主要特性包括:
命令解釋與執行
Shell 的主要功能是讀取和解釋使用者輸入的命令,並執行相應的程式或 script (腳本)。 其命令列介面,讓使用者可以直接與系統互動、執行命令並接收即時回饋。
可程式性
Shell 本身就是一種程式語言,允許使用者編寫 script 來自動執行重複性任務、組合命令以及創建複雜的工作流程。這包括變數定義、控制流結構(條件和循環)以及函數定義等功能。
I/O導向和管道
使用者可以使用 >、>>、< 和 | 等運算子將命令的輸入和輸出重新導向到檔案或其他命令。這個特性提供 Unix 強大的資料操作和處理功能。
彈性的環境自訂
Shell 允許使用者透過環境變數、別名和 Shell 初始化檔案自訂其工作環境,這些檔案可以定義搜尋路徑、提示字元和其他設定。
具備正規表示式(Regular Expression)能力的字串匹配
Shell 提供使用萬用字元(例如 *、?)進行檔案名稱展開以及正規表示式進行字串匹配的機制,從而大幅簡化檔案操作和字串搜尋與處理。
程序管理
Shell 簡化了基本的程序管理,包括在背景執行命令、暫停和復原程序以及管理作業控制。
Shell 語法的版本
   
Shell 的語法有許多不同的版本,不同語法寫就的 script 互不相容, Shell A 語法寫出的 shell script 不能用 Shell B 來執行。 比較風行的版本是原始版的 Bourne Shell, Korn Shell, Bash, 以及C Shell,蘋果電腦的 Mac OS X中的 Terminal 支援 Bash.
Shell 的效率
   
Shell 是一種直譯器 (Interpreter), 亦即不經過編譯(compiling)而一面剖析 script 一面執行。 其效率雖然較低,但如果是用來執行一些短小不常用的任務, 執行的時間可能不過數秒或數分鐘時間,所佔的總時間比例並不高, 但可省下使用者很多的人工時間,整體而言利大於弊。
Shell 的程式語言能力
   
Shell 的程式語言元素包括變數定義、控制流程結構(條件和循環)以及函數定義等功能。 但是終究不同於傳統完整的高階程式語言,Shell 的資料型態只有"字串",沒有 複雜的資料結構,例如陣列與指標等,只有非常有限的算數運算能力, 但是也是因為功能陽春,對於簡單任務的 script 撰寫,非常輕鬆。
Wed Dec 3 11:37:07 CST 2025 Untitled Document
Unix 之優缺點
Wed Dec 3 11:37:07 CST 2025 Untitled Document
學習 Unix Shell Programing 之方法
Wed Dec 3 11:37:08 CST 2025 Untitled Document
Shell Script 設計小秘訣
Wed Dec 3 11:37:08 CST 2025 Untitled Document
將文字檔設為可執行
可執行檔
   
使用者交付一個可執行檔請求作業系統安排電腦硬體執行。 Shell 接受兩種執行檔格式,一是按照 Unix 指定格式編排的機械碼檔案, 俗稱 Binary file。另一種是文字碼格式的 Shell Script。 電腦硬體可以直接執行機械碼 (Binary File),但不能執行文字格式的 Script。 而一個 shell script 等於是一份指引,指揮shell 將一份份的機械碼檔案找到後送給 電腦硬體去執行。 以下的 script
 
ls
who
就是請Shell找到對應於 ls 及 who 的機械碼檔案並依順序安排電腦去執行。
將文字檔 Shell Script設定為可執行
   
所有執行檔的執行權限都必須打開設定為可執行。 而 shell script,必須額外設定成可讀,才能呼叫 Shell 來執行。 我們可以使用下列命令更改存取權:
chmod u+rx filename
只有使用者自己可讀可以執行,其它人的權限不變
chmod og-rx filename
除了使用者自己的權限不變之外,其它人不可讀不可以執行
chmod ug+xr filename
只有使用者自己以及同群使用者可讀可以執行,其它人的權限不變
chmod +x filename
所有人都可以執行
chmod -x filename
所有人都不可以執行
Wed Dec 3 11:37:08 CST 2025 Untitled Document
Shell如何執行機械碼執行檔與shell script?
 
Shell scripts 是 ASCII 檔
   
Shell 都是以 ASCII 格式寫就,而 Shell 對二進位的執行檔與 ASCII 格式的 Shell script 一視同仁,都是當作執行檔來執行,但是對於二進位執行檔是直接 交付作業系統執行,但如果對於 shell script, 則由Shell 本身來執行,其實將一個 shell script 細部動作拆開來看,就能發現, 隨著shell script 的執行,一個個二進位執行檔被抓出來交給作業系統去 執行。 Shell 本身就是一個 程序 (Process)負責將一個 shell script 的內容轉成 一道道的命令執行。
呼叫 Shell
   
當Shell執行一個使用者交付的指令時(也就是執行一個執行檔), 它會將任務交給 Kernel,由 Kernel 啟動一個新的程序(process)來執行這個任務。 如果這個任務本身是一個機械碼執行檔,Kernel 就直接執行它。 (讀者可參考作業系統相關書籍瞭解相關細節。)
   
如果任務是一個shell script, Kernel會回給 Shell 一個 "not executable format file"的訊息, 意思是交付的檔案格式不符(非機械碼執行檔)。 接到此錯誤訊息後,Shell將會把它視為一個 shell script,並啟動 一個新的 Shell 子程序來執行它。
   
如果使用者所交付的shell script 不是以預設的 Shell 而是使用另一種版本的Shell 語法寫成的。 使用者有兩種方式來改變:
  1. 由使用者直接呼叫該Shell 的機械碼執行檔,例如:
    /bin/csh script-file    #假設 csh 是放在 /bin 下 
    
  2. 在script 的第一行敘明所要呼叫程式的絕對路徑
    #!/bin/csh script-file      #假設 csh 是放在 /bin 下 
    
上面兩種方式除了呼叫各種shell之外,其實也可以請求呼叫任何機械碼執行檔, 例如:
使用 AWK
#! /bin/awk -f
使用 /usr/bin/perl
#! /usr/bin/perl
Wed Dec 3 11:37:08 CST 2025 Untitled Document
檢視執行軌跡 (Execution Tracing)
檢視執行軌跡
   
如欲檢視Shell在執行一個 shell script 期間的詳細步驟,尤其是 變數的變化,有兩種方式為之:
  1. 下指令時利用 '-x' option 直接呼叫
  2. 下一個 "set -x" 指令,或放在script 中
 
範例
#-------------------
#myscript
#-------------------
who | wc -l
% sh -x myscript    #呼叫 /bin/sh 來執行 myscript
+ who #系統印出執行細節
+ wc -l #系統印出執行細節
9 #系統印出上線人數
% set -x #turn on trace
% who | wl -l    #執行一個指令
+ who #系統印出執行細節
+ wc -l #系統印出執行細節
9 #系統印出上線人數
% set +x #turn off trace
Wed Dec 3 11:37:08 CST 2025 Untitled Document
Script的基本結構及觀念
   
最簡單的 shell script 是將要執行的指令放在 script 內以批次方式執行。而 Shell Script的語言結構 和一般高階語言沒有什麼兩樣, 也和高階語言一樣有變數,流程控制及副程式。 這些使得shell script的功能從原始的工作流程安排提升 為更加強大的高階程式語言。
Wed Dec 3 11:37:09 CST 2025 Untitled Document
內建指令與外部指令
   
作為一個完整的程式語言, Shell 本身自然有內建指令,諸如迴圈控制、變數以及函數等 常見指令,而其他如ls, cat 等常用指令都不是Shell的內建指令, 本書稱之為外部指令以別於內建指令。 外部指令其實就是一個個事先寫好,由系統提供或使用者 自行提供的Shell script 或機械碼執行檔,Shell 內建的指令大部分是用來安排外部指令的 執行順序,真正的工作,都是外部指令在做的。 Shell好像將軍而外部指令是士兵, 將軍負責發出指令指揮士兵遂行作戰任務。我們常見的Shell指令, 例如: ls, cat, grep 等都是外部指令,有興趣的讀者可到 /bin 及 /usr/bin 等相關目錄下,可以看到這些指令的機械碼執行檔。
 
外部指令之執行步驟 以及 如何找到外部指令
1. 啟動一個新的子程序,執行這個任務。
2. 在新的子程序中,從變數 $PATH 中所列出的目錄中搜索使用者所要求的 外部指令執行檔。
3. 在新的子程序中,執行所得到的執行檔。
Wed Dec 3 11:37:09 CST 2025 Untitled Document
如何執行一個程式 (Script)
   
在Bourne Shell中使用者有五種方法請 Shell 執行一個 script, 而這五種方式所產生的結果有些許的不同。 以下的說明中,假設 script 的檔名是'myscript'。
使用方式 檔案權限 使用者下指令 系統執行方式
1. 直接鍵入script檔名 script 必須為可讀可執行,
且script所在的目錄必須記錄在環境變數 $PATH 之內
myscript  
呼叫一個子程序 (subshell) 執行 myscript
2. 使用sh命令,
將檔案名稱作為引數,

也可以選用其他shell,例如 csh, bash, ksh 等

script 必須為可讀
sh myscript  
呼叫一個子程序 (subshell) 執行 myscript
3. 使用"."命令,
將檔案名稱作為引數
script 必須為可讀
  • myscript    
  • 這個方式和和使用sh命令相似,只不過它不像sh一般會產生新的子程序,相反的, 它會在原有的程序下完成工作。 在這種執行方式下,一個script 可以改變原有程序的環境變數 內容。前者方式則無法改變。
    4. 使用exec命令,
    將檔案名稱作為引數
    script 必須為可讀
    exec myscript  
    
    系統執行方式同上,但這個 Shell 將會被所執行的命令所取代。當這個命令執行完畢之後,這個 Shell也會隨之結束。
    5. 使用命令替換
    (Command Substitution),

    用以取代 script 的某一段文字,
    類似變數的展開

    script 必須為可讀可執行,
    且script所在的目錄必須記錄在環境變數 $PATH 之內
    `myscript`   
    or
    $(myscript)
    呼叫一個子程序 (subshell) 執行 myscript,將執行的輸出取代 `myscript`字串。 例如以下的script就是把 filelist 的內容抓出來送給迴圈當作引數,逐一印出檔案名稱以及內容:
    for i in `cat filelist`
    do
      echo "========= File $i ========="
      cat $i
    done 
    
    下節有更多的解釋。
    Wed Dec 3 11:37:09 CST 2025 Untitled Document
    命令替換 (Command Substitution)
       
    在C 語言內,執行一個函數 (Function)時,Function 所傳回的值會被程式抓住直接使用。 而在 shell script 中,各外部指令可能會將執行結果輸出到 STDOUT,如果要將指令的執行結果抓回script內運用時,要如何做? Shell 提供命令替換 (Command Substitution)機制。
    範例
    Script: command-substitution 執行結果
    echo "Current directory is   `pwd` "
    

    echo "Current directory is   $(pwd) "
    
    Current directory is /usr/lien   
    #------------------------------- 註: pwd 這個命令輸出"/usr/lien",
    而後整個字串代替原來的`pwd`,填入字串

     
    更多例子:
    today=`date`
    echo "Today is $today"
    
    echo "Today is `date`"
    
    echo "Today is $(date)"
    
    #----------------------
    # 印出目錄下所有檔案
    #---------------------- 
    for i in  `ls` 
    do
    echo "==== File Name: $i ===="
    cat $i
    done
    
    Wed Dec 3 11:37:09 CST 2025 Untitled Document
    輸出指令: echo and printf
    echo
     
    echo 是最常用的 shell script 輸出指令。 echo 將後面所帶的字串印出到 STDOUT,echo 其實是一隻 外部程式,將引數逐一列出,中間用一個空白隔開, 最後印出一個「換行」的符號。 例如:
    $ echo This is a    book.
    #-------------------------------------
    結果: This is a book.
    
       
    有時候在設計 shell script 時,不希望 echo 在最後印出一個 「換行」的符號,要讓游標停留在該行的尾巴, 接下來無論是 使用者鍵入任何字串,或script 再印出東西來,都會接在游標後面, 而不會跳到新的一行。(相當於在C 語言的printf 中的引數後面沒有加上 "\n")。 System V 提供一個特殊的控制符號 "\c", 當使用者交付給 echo 的引數後面,帶有"\c" 時,echo 就不會印出「換行」。有些版本的 Shell 則在 echo 提供一個option "-n" , 當使用者在下 echo 指令時 如果帶有 -n option 時,也能達到同樣的效果。
    
    #-----------------------------------
         Some version of Shell
    #-----------------------------------
    $ echo -n "Enter your name: " 
    
    
    #-----------------------------------
         System V version 
    #-----------------------------------
    $ echo "Enter your name: \c" Print prompt
    
     
    echo 認得的特殊符號

    符號 說明
    \a
    Alert character, usually the ASCII BEL character
    \b
    Backspace
    \c
    Suppress the final newline in the output. Furthermore, any characters left in the argument, and any following arguments, are ignored (not printed)
    \f
    FormFeed
    \n
    NewLine
    \r
    CarriageReturn
    \t
    Horizontal tab
    \v
    Vertical tab
    \\
    A literal backslash character
    \0ddd
    Character represented as a 1- to 3-digit octal value.
    範例
    #-------------------------------------------------------------
    #印出一個「回車」(Carriage Return)符號,效果相當於「換行」(newline)
    #------------------------------------------------------------- 
    echo '\012'   
    
    printf
       
    Shell 現在也提供類似 C 語言中的 printf 指令,讓程式設計師可以指定輸出的格式:
    #-----------------------------------------------
    $ printf "This script prints '%s, %s!'\n" Hello world
    #-----------------------------------------------
    #結果: This script prints 'Hello, world!'
    
    Wed Dec 3 11:37:10 CST 2025 Untitled Document
    Read (從 STDIN 輸入)
       
    類似 C 語言中的 scanf,Shell 也提供一個從鍵盤 (STDIN) 輸入 的指令,'read' 格式如下:
      read var1 var2 .....
    
       
    • read 後面帶的 var1, var2 等是用來儲存輸入值的變數。這時read會將輸入的字串以空白斷開成一序列的字詞,再逐步將 一個字詞分配給一個變數。
    • 如果輸入的字詞數量比變數數量還多,最後一個變數會將剩下的字詞全部分配給最後一個變數。
    • 如果輸入的字詞數量比變數數量還少,則後面的變數會充值成空字串。
    範例
    echo "Enter your name please:"
    read name
    echo "Hello $name, Happy New Year!"
    
       
    'read'指令可以用來暫停 script 的執行, 在執行 read 時,Shell 會停下來,等使用者輸入任何資訊時, 才會繼續執行,下例中, Shell 會等使用者輸入任何字元或字串, 才會將文件印出於印表機。
    範例
    #-----------------------------
    #Interactive printing 
    #----------------------------- 
    echo "Is Printer ready?"
       read anything 
    lp xxxxxxx
    
    Wed Dec 3 11:37:10 CST 2025 Untitled Document
    算數運算 (Arithmetic Expression)
       
    Shell 的變數都是字串型態,因此非常不利於算數運算, 因此,Shell 並不適合作為大量算數運算的程式語言。 原始版本的 Bourne Shell 並無 內建的運算式,(我們將在後面的章節介紹 POSIX 定義的運算式), 如果真的有需要處理數值運算,我們可以使用expr這個外部指令。 讀者可自行查詢 expr 的用法。 格式如下:
    expr expression
    
       
    expression是由字串以及運算子所組成, 每個字串或是運算子之間必須用 空白隔開 。下表是運算子的種類及功能,而優先順序則以先後次序 排列,我們可以利用小括號來改變運算的優先次序。 其運算結果則輸出至標準輸出上。其運算符號及意義如下:
    符號 說明
    :
    字串比較。
    比較的方式是以兩字串的第一個字母開始, 而以第二個字串的最後一個字母結束。 如果兩者相同時,則輸出第二個字串的字母個數,如果不同時則傳 回0 。
    右邊可以是 字串的 Regular Expression.
    + - * / %
    加減乘除,取餘數
    < = >
    數值比較 (Numerical Comparison)
    & |
    邏輯運算,AND 及 OR
       
    當expression中含有"*", "(", ")" 等符號時,必須在其前面加上"\" ,以免被 Shell 解釋成其它意義。
    例: expr-example 執行結果
    expr 2 \* \( 3 + 4 \)       
    
       
    其輸出為 14
    上面的式子,如果表達成下面這幾個樣字、都是不正確的。
    expr 2 * ( 3 + 4 )    #錯誤,括弧前面少了 \  
    
    expr 2 \* \( 3+4 \)   #錯誤,各項之間必須有空白隔開 
    
     
    更多例子
    expr 5 \* 3
    expr 5 / 3
    expr 5 % 3
    a=0
    a=`expr $a + 1`
    echo $a
    
    echo "Enter a number:"
    read x
    double=`expr 2 \* $x`
    echo "$x times 2 is $double"
    
       
    印出 1 至 1000 數字
    #-----------------------------------------------
    #counter1000: generate numbers from 1 to 1000
    #----------------------------------------------- 
    count=1
    while [ $count -le 1000 ]
    do
       echo $count
       count=`expr $count + 1 `
    done
    
    Wed Dec 3 11:37:10 CST 2025 Untitled Document
    Shell 語言之主要元素
     
    變數 (Variables) 及相關動作
       
    • 變數型態都是字串
    • 變數展開 (Variable substitution)
    • 變數展開之擴充
     
    用於流程控制的語言元素 (Control Flow Primitives)
       
    條件式執行 (Conditional Execution)
    if-then-else
    case
       
    迴圈式執行 (Repetitive Execution)
    for
    while
    until
       
    無條件程式跳轉 (Unconditional transfer of control)
    goto
    break
    continue
    exit
     
    函數 (Function)
       
    • 與一般傳統函數大同小異
    • 使用位置變數 (Position Variables: $1, $2, ... $9, $*) 傳遞函數之參數
    Wed Dec 3 11:37:11 CST 2025 Untitled Document
    變數
     
    變數的型態
       
    原始Bourne Shell的變數型態只有「字串」,包含下列字元:
    • 大小寫英文字母 (upper and lower case letters)
    • 數字 (digits)
    • newline 符號
    • 空白
    • tab
    • 一些特殊符號
     
    變數的種類
       
    變數的種類可歸類成下列幾種:

    使用者變數
    唯讀的使用者變數
    系統變數(環境變數)
    特殊變數
       
    使用set 指令可列出一個程序所定義的變數:
    set
    
    Wed Dec 3 11:37:11 CST 2025 Untitled Document
    使用者變數
     
    變數之設定
       
    用'='設定變數
    var=string
    
    注意:'=' 左右兩邊不可有空白:
     正確: $num=10
     錯誤 $ num =10
     錯誤 $ num= 10
     錯誤 $ num = 10
    
    可以指定 Null 到一個變數:
    $ tech=
    $ tech=""
    
     
    變數之展開
       
    在變數名稱前加上一"$" 號即可展開變數之值, 必要時,變數名稱前後可加上大括弧'{}' 以避免產生混淆: 例如下列的指令及回應:
    %x=15
    %echo x
    x
    %echo $x
    15
    %echo ${x}
    15
       
    變數之展開有許多變化,請見後續介紹。
    Wed Dec 3 11:37:11 CST 2025 Untitled Document
    使用者變數命名規則
    使用者變數命名規則
    第一個字符必須是英文字母或 "_"
    第二個以後的字符可以是任意序列的Alphanumeric字元(英文字母、數字)及 "_"
    英文字母是 Case Sensitive,有區分大小寫
    總長度由各系統自主決定,沒有統一規定
       
    合格之 Shell 變數名稱之例:
    
    HOME
    SYSTEM_VERSION
    tech
    Myfile2
    _pay2me_
    
    
       
    不合格之 Shell 變數之例:
    acct!
    2ndfile
    $owed
    
       
    軟體工程建議了許多變數命名的規則,讀者請自行參考。
    Wed Dec 3 11:37:11 CST 2025 Untitled Document
    唯讀的使用者變數
     
    唯讀變數
       
    唯讀變數和使用者變數相似,只不過這些變數不能被改變。 只要加上 readonly 關鍵詞就可將使用者變數設成唯讀:
    readonly var
    
    注意:系統變數不可以設定成唯讀的。

    readonly 後面如果沒有接變數名稱,則會列出所有唯讀的變數。 下列的指令及回應是範例:

    %name1=Clinton
    %echo $name1
    Clinton
    %readonly name1
    %name1=John
    -sh: name1: is read only

    Wed Dec 3 11:37:11 CST 2025 Untitled Document
    環境變數
       
    環境變數和使用者變數相似,只不過此種變數會將其值傳給其子程序 (subshell)所執行的命令。 所以一個子程序會繼承所有父程序的環境變數。 一個shell script 可以用 export 指令將一個普通的使用者變數變成 環境變數,不但該程序可以用到,其所衍生的子程序也都可以用到。 等到該程序結束,所有定義的環境變數將被清除,不復存在。
     
    export 宣告環境變數

       
    在原始 Bouorne Shell 中,必須分兩步驟進行:
    name=Clinton
    export name
    PATH=$PATH:/usr/local/bin 
    export PATH 
    
       
    在 POSIX標準中,只需一個步驟即可:
    export name=Clinton
    export PATH=$PATH:/usr/local/bin
    
     
    列出所有環境變數
    $ export -p   #Print current environment 
    
     
    Add variables to a program's environment without permanently affecting the environment of the shell or subsequent commands.

      PATH=/bin:/usr/bin awk '...' file1 file2
    
    This changes the value of PATH only for the execution of the single awk command. Any subsequent commands, however, see the current value of PATH in their environment.
    Man Page of 'export' Wed Dec 3 11:37:12 CST 2025 Untitled Document
    POSIX 定義的內建環境變數
    $ENV
    Used only by interactive shells upon invocation; the value of $ENV is parameter-expanded. The result should be a full pathname for a file to be read and executed at startup. This is an XSI requirement.
    $HOME
    Home (login) directory.
    使用者自己的目錄
    $IFS
    Internal field separator; i.e., the list of characters that act as word separators. Normally set to space, tab, and newline.
    $LANG
    Default name of current locale; overridden by the other LC_* variables.
    $LC_ALL
    Name of current locale; overrides LANG and the other LC_* variables.
    $LC_COLLATE
    Name of current locale for character collation (sorting) purposes.
    $LC_CTYPE
    Name of current locale for character class determination during pattern matching.
    $LC_MESSAGES
    Name of current language for output messages.
    $LINENO
    Line number in script or function of the line that just ran.
    $NLSPATH
    The location of message catalogs for messages in the language given by $LC_MESSAGES (XSI).
    $PATH
    Search path for commands.
    執行命令時所搜尋的目錄
    $PPID
    Process ID of parent process.
    $PS1
    Primary command prompt string. Default is "$ ".
    在命令列時的提示號
    $PS2
    Prompt string for line continuations. Default is "> ".
    當命令尚未打完時,Shell 要求再輸入時的提示號
    $PS4
    Prompt string for execution tracing with set -x. Default is "+ ".
    $PWD
    Current working directory.
     
    其他幾個有用的環境變數
    $TZ
    時區
    $MAILCHECK
    每隔多少秒檢查是否有新的信件
    $MANPATH
    man 指令的搜尋路徑
    Wed Dec 3 11:37:12 CST 2025 Untitled Document
    Env
     
    The env command may be used to remove variables from a program's environment, or to temporarily change environment variable values:

    Man Page of 'env'

    env -i PATH=$PATH HOME=$HOME LC_ALL=C awk '...' file1 file2
    

       
    The -i option initializes the environment; i.e., throws away any inherited values, passing in to the program only those variables named on the command line.
    Wed Dec 3 11:37:12 CST 2025 Untitled Document
    Unset
     
    Removes variables and functions from the running shell.
    unset full_name   #Remove the full_name variable 
    unset -v first middle last   #Remove the other variables 
    
     
    Use unset -f to remove functions:
    who_is_on ( ) {   #Define a function 
    who | awk '{ print $1 }' | sort -u   #Generate sorted list of users 
    }
    unset -f who_is_on   #Remove the function 
    

    Man Page of 'unset'

       
    Early versions of the shell didn't have functions or the unset command.
    POSIX added the -f option for removing functions, and then added the -v option for symmetry with -f.
    Wed Dec 3 11:37:12 CST 2025 Untitled Document
    特殊變數
       
    有些變數是一開始執行 script 時就會設定,並且不可以加以修改, 此種變數稱為「特殊變數」(有些書稱為「唯讀的系統變數」)。

    $0
    記錄這個 script 的執行名字 (通常是script的檔案名稱)
    $d
    位置變數,記錄這個 script 的第d個引數,d 是數字
    $*
    記錄這個 script 的所有引數,展開後成為一個字串
    $@
    記錄這個 script 的所有引數,展開後每一個引數成為一個獨立字串
    $#
    記錄這個 script 的引數個數
    $$
    記錄這個 script 的PID (Process ID)
    $!
    記錄執行上一個背景指令的PID (Process ID)
    $?
    記錄執行上一個指令的返回值
    Wed Dec 3 11:37:13 CST 2025 Untitled Document
    POSIX Built-In Shell Special Variables
    Variable Meaning
    $# Number of arguments given to current process.
    $@ Command-line arguments to current process. Inside double quotes, expands to individual arguments.
    $* Command-line arguments to current process. Inside double quotes, expands to a single argument.
    $- (hyphen) Options given to shell on invocation.
    $? Exit status of previous command.
    $$ Process ID of shell process.
    $0 (zero) The name of the shell program.
    $! Process ID of last background command. Use this to save process ID numbers for later use with the wait command.
    Wed Dec 3 11:37:13 CST 2025 Untitled Document
    引數與位置變數
    引數(Argument) 及 位置變數(Position Variables)
       
    當使用者下一個指令時,後面經常會給一些參數,稱為引數 (argument),例如輸入資料的檔案名稱等。而 shell script 必須能取得這些引數,憑以執行使用者交付的任務。 Shell 會主動取得這些引數,然後放在幾個特殊系統變數內,稱為 「位置變數」 (Position Variable)內,對應如下:
    使用者指令 command-name arg1 arg2 ...... ...... arg9
    Shell Script 內部
    位置變數
    $0 $1 $2 .... .... $9
    其中 $0 是指令名稱,內建指令或外部指令, 如果是外部指令,則為執行檔之檔案名稱。

    範例: position-var-example
    echo first arg is $1
    echo tenth arg is ${10} 
    

     
    特殊變數 $* 是一個字串,儲存有所有的引數, 下面三個 script 是相同的:
    #Assume five arguments
    #-------------------------
    for i in $1 $2 $3 $4 $5
    do
    grep "^$i" /etc/passwd
    done
    
    #same as previous script
    #-------------------------
    for i  in $*
    do
    grep "^$i" /etc/passwd
    done
    
    #same as previous script
    #-------------------------
    for i 
    do
    grep "^$i" /etc/passwd
    done
    #-------------------------
    #"for i" 後面的 argument list 可以省略
    
       
    有關 for 迴圈的介紹請參見相關章節

     
    特殊變數 $# 是一個字串,記錄了位置變數的數量
    if [ $# -eq 0 ]
    then
     echo Usage: ........
     exit 1
    else
    _......
    _.....
    fi
    
       
    有關 if-then-else 的介紹請參見相關章節
     
    位置變數雖是唯讀變數, 但可用 'set' 重設,方法如下:
    set string
    
    如此$*的值即為string,而分解後則會放入對應的位置變數。例如:
    指令 執行結果
    set `date`; echo $6 $2 $3, $4 2023 Mar 11, 20:41:59

    Man Page of 'set'

    Wed Dec 3 11:37:13 CST 2025 Untitled Document
    Shift
       
    我們可以使用 shift 命令將位置變數往前移一格或數格, 捨棄前幾個變數,後面的變數則往前遞補。
     
    Shift [n]  #將位置變數捨棄n個
    
    範例
    Script 執行結果
    # ----------------------------
    # Script: shift-test
    # ---------------------------- 
    echo Filename: $0 echo Arguments: $* echo No. of args.: $# echo 2nd arg.: $2 shift echo No. of args.: $# echo 2nd arg.: $2 set hello, everyone echo Arguments: $* echo 2nd arg.: $2
    #----------------------------
    #Execute: shift-test 20 30 40 50 
    #---------------------------- 
    Filename:    shift-test 
    Arguments:    20 30 40 50 
    No. of args.:   4 
    2nd arg.:   30 
    No. of args.:   3 
    2nd arg.:   40 
    Arguments:    hello, everyone 
    2nd arg.:   everyone 
    
    Wed Dec 3 11:37:13 CST 2025 Untitled Document
    Shift 有什麼用?
       
    初學Shell Programming 的人可能用不上 shift 這個指令,但如果 要設計一個不定數量的引數的程式時,這是一個必要的指令。
       
    為了讓一個 script 可以重覆使用,script 的設計通常不會將輸入參數寫死在 script 內,而是讓使用者在呼叫 script 時再利用引數給定, 就像系統所提供的指令大多是如此設計。 引數的數量有時候不是固定的,有多有少, 例如 'cat' 這個指令就能接受任意數量的引數如下;
    cat 1.txt *.csv
    
    難題一: 如何處理不定數量的引數?
       
    這類 script 可以用如下的形式逐一處理輸入檔案(兩個 script 作用相同):
    for i in   $* 
    do
      do-something $i  #一個指令,取用一個引數 
    done
    
    for i do do-something $i done
    上面這個script中,'$*'在展開後,會將所有的引數納入, 一舉解決引數的數量不固定的難題。 但是如果引數的前幾個與後面的引數 有不同的意義時,上面的 script 就不適合了。
    難題二: 不均勻引數,同時有固定數量及不定數量的引數
       
    假設script的設計很特殊,第一個 引數是一個txt 檔,而第二個引數以後是 不定數量的 csv 檔, 而script被要求對不同種類的檔案 採取不同的處理方式,這時就用得上 shift 了。
    #讀取第一個txt檔,將之後的所有csv 檔內的逗號改成空白,全部串接在一起,輸出到 STDOUT
    #=========================================================================
    cat $1  
      shift  #捨棄第一個引數
    for i 
    do
      sed 's/,/ /g' $i  #利用sed將逗號改成空白
    done
    
    Wed Dec 3 11:37:13 CST 2025 Untitled Document
    I/O Redirection
     
    Syntax
    >file write standard output to file
    >>file append standard output to file
    >&m write standard output to file descriptor m
    <file read standard intput from file
    <&m read standard input from file descriptor m
    <<word read standard input from a "here document"
     
    Any of the file redirection syntaxes can be prefixed with a file descriptor to specify a file to be used in place of the default file for that syntax.
    command 2> file write standard error to file

    #discard both standard output and standard error
    command > /dev/null 2>&1
    #wrong
    command  2>&1    > /dev/null
    
    Reading Files
    while read LINE
    do
    command
    done < file
    
    Truncate a File
    > file
    
    : > file
    cat /dev/null > file
    Wed Dec 3 11:37:14 CST 2025 Untitled Document
    Avoid File From Truncated by I/O Redirection
    set -C
     
    Execute command 'set -C' enables the shell's noclobber option.
       
    redirections with plain > to preexisting files fail.
    '>|' overrides the noclobber option
       
    a feature borrowed from the Korn shell
       
    standardized since 1992,
       
    some systems may not support it
    Wed Dec 3 11:37:14 CST 2025 Untitled Document
    流程控制
       
    如同一般的程式語言,Shell 也能控制流程,在Shell script 或命令列中,可用控制指令安排指令的執行順序,包括條件判斷(如 if, case 語句)、迴圈(如 for、while、until 語句)以及跳轉 (goto, break, continue, exit)等。
     
    控制流程的常見用途

    自動化任務 在 script 中設定條件判斷和循環,可以自動化複雜的任務。
    靈活性 允許 script 根據不同的輸入或環境執行不同的操作。
    錯誤處理 透過條件判斷,可以處理程式執行過程中可能出現的錯誤狀況。
     
    主要的控制流程結構
    條件語句(if, case) 根據條件是否成立來決定要執行哪些指令。
    if-then
    當條件為真時執行一組指令。
    if-then-else
    當條件為真時執行一組指令,否則執行另一組指令。
    case
    根據某一個變數或某一計算式的值,挑選符合的一組 或多組程式碼區塊執行。
    迴圈語句(for、while、until) 重複執行一組指令,直到滿足特定條件為止。
    for
    通常用於遍歷清單中的項目。
    while
    當條件為真時持續執行指令。
    until
    當條件為否時持續執行指令。
    跳轉語句(goto, break, continue, exit) 將執行流程跳到 script 中的另一個位置。
    break
    從迴圈程式中跳出,繼續執行迴圈之後的指令
    continue
    從迴圈程式中跳出,繼續執行迴圈之下一個循環
    exit
    終結程式
    goto
    goto可以跳到任一個位置繼續執行,但要避免濫用 goto。 濫用 goto 會導致程式碼難以閱讀和維護, 結構化程式設計鼓勵使用如 if-then-else、for 和 while 等控制結構來替代 goto,某些版本的Shell(例如 Bash)就乾脆不提供 goto指令。
    Wed Dec 3 11:37:14 CST 2025 Untitled Document
    測試指令的標的物 (Target of Condition Test)
    條件式語句的測試標的物
       
    條件式及循環式的指令都會需要一個條件測試其格式如下:
    if   condition-command 
    then       
        commands 
    fi                
    
    while    condition-command 
    do              
        commands     
    done            
    
    until   condition-command 
    do              
        commands     
    done            
    
    其中 condition-command 是任何指令,其執行的結果成功與否就 作為條件式及循環式語句的測試標的。 (註;一個指令執行如果是成功的,其回傳的 Status 為0, 否則為其他數值。) 以下是一個範例, script 從STDIN反覆讀進資料, 輸出於 STDOUT,直到STDIN全部耗盡為止。
    while read a
    do
       echo $a
    done
    
       
    使用者在撰寫一個 script 時,是可以利用 'exit n' 將 Status 設定為 n。當然正常使用者應該不會在執行成功時,將 Status 設定為 0 以外的值。 舉例如下:
    echo "Success"
    exit 0
    
    echo "Failure"
    exit 4
    
    test 指令
       
    一般程式語言最常見的測試內容是比對兩個數值、 兩個字串或兩個物件,而比對條件則是「相等」、「大於」、「小於」 、「不等」等。 作為一個程式語言,Shell 當然也提供數值與字串的比較。 但是,Shell 的強項並非數值運算,而是檔案與程序的處理。 最需要下列相關測試: 檔案是否存在?讀寫權限為何?子程序的執行是否成功?等等。 Shell 提供一個內建指令,test,作為種種測試之用。 Script 的流程根據 'test' 指令的執行成功與否來決定。
    Wed Dec 3 11:37:14 CST 2025 Untitled Document
    test 指令
       
    Shell 提供一個內建的 test 指令,作為字串比對、數值比較,檔案性質 測試之用,格式如下:
    test expression
    很多版本的 Shell 可用 中括號 "[ ]" 將條件式括起來,取代 test 指令。
    [ expression ]
    Ksh 可用威力更大具有 regexp (正規表示式) 的 [[ ]]
    [[ expression ]]

    單項測試

    字串比對(String Comparison)
    string string不為空白字串
    -n string string的長度大於0
    -z string string的長度等於0
    string1 = string2 string1等於string2
    string1 != string2 string1不等於string2
    數值比對(Numerical Comparison)
    int1 -gt int2 int1大於int2
    int1 -ge int2 int1大於等於int2
    int1 -eq int2 int1等於int2
    int1 -ne int2 int1不等於int2
    int1 -le int2 int1小於等於int2
    int1 -lt int2 int1小於int2
    檔案性質測試 (File Test)
    -r filename 檔案為可讀取
    -w filename 檔案為可寫入
    -x filename 檔案為可執行
    -f filename 檔案為一般檔
    -d filename 檔案為目錄
    -s filename 檔案為非空的一般檔
       
    以下是可以一看即知的簡單實例:
    data1=4
    data2=6
    if test $data1 -eq 4 
    then 
       echo "Data1 is 4"
    fi
    if [ $data2 -ne 6 ]
    then 
       echo "Data2 is not 6"
    fi
    
    複合測試
       
    expression中可包含一個以上的判斷準則以作為測試條件。 兩準則間用 "-a" 代表邏輯 AND 運算,而 "-o" 代表邏輯 OR 運算, 而在準則前放置一個 "!" 符號則代表 NOT 運算。如 果沒有括號,則優先權順序為則為
    "!" 先於 "-a" 先於 "-o"
    如果有括號時,由內而外優先執行括號內的 test expression, 注意在 Shell 中,括號也可作為將幾個指令組成群組的功能, 如此會產生混淆,因此在 test expreession 中的括號必須加上 escape 符號 "\" 避免衝突。
     
    以下是兩個例子:
    #測試檔案是否可讀兼可寫
    #=========================
    test -r "$filename" -a -w "$filename"
    
    #測試檔案是否可讀或可寫
    #=========================
    [ -r "$filename" -o -w "$filename" ]
    
    test 指令的改進
       
    test 指令在數值比對以及複合測試的語法與一般程式語言差異相當大, 以致影響 script 的可讀性,而且造成初學者的學習障礙,因此 ksh 用"[[ expression ]]"改善了這個缺點,並加入了 regexp 的能力,大幅改善了test 指令的缺點。
     
    範例
    #範例 1:測試字串是否相等
    #================================
    if [[ $name == "John" ]]; then
      echo "Hello John!"
    fi
    
    #範例 2:測試數字是否大於
    #================================
    if [[ $age > 25 ]]; then
      echo "You are older than 25."
    fi
    
    #範例 3:使用邏輯運算符組合多個條件
    #================================
    if [[ $name == "John" && $age > 25 ]]; then
      echo "John is older than 25."
    
    #範例 4:使用 regexp
    #================================
    if [[ $foo =~ "A*"  ]]; then
      echo "Variable foo starts with letter A."
    
    Wed Dec 3 11:37:14 CST 2025 Untitled Document
    流程控制 - if
       
    第一個流程控制是 "if",學過程式語言的讀者,應該很熟悉,不需特殊說明, 讀者應可瞭解其用法,以下是各種格式:
     
    if then
    if condition-command
    then       
        then-commands 
    fi                
    
    if test condition
    then       
        then-commands 
    fi                
    
    if [ condition ]
    then       
        then-commands 
    fi                
    
     
    if-then-else
    if condition-command 
    then       
        then-commands 
    else           
        else-commands
    fi
    
     
    elif
    			 
    if [ condition-command-1 ]
    then        
        then-commands
    elif [ condition-command-2 ] 
    then           
        then-commands
    else         
        else-commands
    fi
    
    Wed Dec 3 11:37:15 CST 2025 Untitled Document
    指令的串接: && 及 ||
       
    上節的script 可以進一步改寫成比較簡潔美觀的 script 如下:
    data1=4
    data2=6
    [ $data1 -eq 4 ] && echo "Data1 is 4"
    [ $data2 -eq 6 ] || echo "Data2 is not 6"
    
    其中,'&&'這個符號的意思是 前面指令執行的結果如果是成功的,就執行後面的指令。 而'||'這個符號的意思是 前面指令執行的結果如果是失敗的,就執行後面的指令。 這樣的寫法,更簡單清楚,更容易分析與維護。
     

     command1 && command2    
    =
     
    if command1
    then
       command2
    fi
    

    例: mkdir newdir && cp thisfile newdir
    
     

     nbsp; command1 || command2   
    
    =
    if ! command1
    then
       command2
    fi
    

    例: 
    rm myfile || echo "ERROR: cannot remove myfile"
    Wed Dec 3 11:37:15 CST 2025 Untitled Document
    Logical NOT AND and OR
       
    && || 也可以用在 測試條件式中連同作為 AND 及 OR ,連同 NOT 作為 邏輯運算的運算子 (logical operator)。
     
    NOT
    if ! grep QQ fileX > dev/null
    then
      echo QQ not found in fileX
    fi 
    
    =
    if grep QQ fileX > /dev/null
    then
    :    # do nothing 
    else
      echo QQ not found in fileX
    fi
    
     
    AND
    if grep pattern1 myfile && grep pattern2 myfile
    then
       echo myfile contains both patterns
    fi 
    
     
    OR
    if grep pattern1 myfile || grep pattern2 myfile
    then
       echo Neither pattern1 nor  pattern2 found in myfile 
    fi
    
    Wed Dec 3 11:37:15 CST 2025 Untitled Document
    流程控制 - If 之例
       
    以下是一個三個版本的同一例子:
    使用情境
    chkarg 測試使用者在執行這個 script 是否有加任何 位置變數,如有,則印出來。
    用法
    chkarg [anything]
    Script
    chkarg_v1
    #-------------------------------------
    # Script: chkarg v1
    #------------------------------------- 
    if test $# -ne 0
    then           
        echo Arg: $* 
    else
        echo "Argument Missing"
    fi                
    

    Script
    chkarg_v2
    #-------------------------------------
    # Script: chkarg v2
    #------------------------------------- 
    if [  $# -ne 0 ] 
    then           
        echo Arg: $* 
    else
        echo "Argument Missing"
    fi                
    

    Script
    chkarg_v3
    #-------------------------------------
    # Script: chkarg v3
    #------------------------------------- 
    [  $# -ne 0 ]  &&  echo Arg: $* || echo "Argument Missing"
    
    執行結果
    % chkarg Hello
    Arg1: Hello
    % chkarg
    Argument Missing
    %
    $* 是所有的 postion variables
    Wed Dec 3 11:37:15 CST 2025 Untitled Document
    流程控制 - If 之例
       
    我們用各種格式來撰寫一個複合的條件式程式碼,讀者可以 觀摩各種增加程式可讀性的技巧。

    for file 
    do 
       if test -f $file
       then
          if  test -r $file
          then 
    	 if test -x $file
    	 then
    	    echo "File $file is 
                readable and executable"
    	 fi
          fi
       fi
    done
    
    for file 
    do 
       if [ -f $file ]
       then
          if  [ -r $file ]
          then 
    	 if [ -x $file ]
    	 then
    	    echo "File $file is 
                readable and executable"
    	 fi
          fi
       fi
    done
    
    for file 
    do 
       if [ -f $file -a -r $file  -a -x $file ]
       then 
           echo File $file is readable and executable
       fi
    done
    
     
    for file 
    do 
    [ -f $file -a -r $file  -a -x $file ] && echo File $file is readable and executable
    done
    
    Wed Dec 3 11:37:16 CST 2025 Untitled Document
    流程控制 - Case
       
    Case 的格式如下:
    case str in
    pat1) command(s);;
    pat2) command(s);;
    pat3) command(s);;
    esac
    
     
    case string in
      pat1) command(s) ;;   #若 $string = pat1, 則執行 command(s) 
    pat2|pat3) command(s) ;;   #若 $string = pat1 或 pat2, 則執行 command(s) 
            *) command(s) ;;   #default case 
    esac
    

    其中供比對的 pattern 可以是以下三種形式中的任一種字串:

    • 使用者給的固定字串;
    • 執行指令得到的結果 (Command Substitution)
    • 含有萬用字元( wildcards 或 meta characters)的字串如下:
       
    Case的功能類似於其他程式語言中的 switch 語句,在處理多個潛在值時,它提供了比多層嵌套的 if-elif-else 結構更易讀、更有效率的替代方案。
      *
    任意字串,包括 null
      ?
    任意字元
      [abc]
    a, b, 或c三字元其中之一
      [a-n]
    從a到n的任一字元
      [!abc]
    a, b, 或c以外的字元
      |
    多重選擇
     
    以下是兩個例子:
    case $# in 
       0) echo Usage: xxxxxx ;;
       1|2) process data ;;
       *) echo Usage: xxxxx ;;
    esac
    
       echo 'Enter A, B, or C: \c'
       read letter
       case $letter in
         A|a) echo 'You entered A.';;
         B|b) echo 'You entered B.';;
         C|c) echo 'You entered C.';;
         *) echo 'Not A, B, or C';;
       esac
    
    Wed Dec 3 11:37:16 CST 2025 Untitled Document
    流程控制 - for 迴圈
       
    'for'迴圈是另個重要的流程控制。其格式如下:
    for var in arg-list 
    do               
        commands      
    done             
    
    var 是迴圈變數,每一圈執行時,會從 arg-list 中逐次取用一個代入迴圈變數中

    範例 Script 執行結果
    for i in xx yy zz 
    do              
        echo $i       
    done              
    
    xx
    yy
    zz
    
    Questions
  • What is the value of $i the first time through the loop?
  • What is the value of $i when the loop complete?
  •  
    將迴圈的輸出轉向到檔案
    使用情境
    利用迴圈合併數個檔案,各檔案之間以分隔線及檔名隔開,存到 outfile
    用法
    concatefile  file-list
    
    Script
    concatefile
    #-------------------------------------
    # Script: concatefile
    #------------------------------------- 
    for i in $*
    do
    echo "====== FILE NAME: $i  ========"
    cat -n $i   # -n 這個 option 會將讀入的資料加上 line number 
    done > outfile
    
       
    這樣的一個小script,就可以遠遠超過視窗系統的效率,節省很多時間。

    假設一個使用者利用 C 語言在開發一個複雜的程式,而原始檔分散在數十甚至 上百個小檔案中,在開發過程中經常要反覆的檢查各原始碼,單純依靠終端機 是很痛苦的事,此時就可以利用 concatefile 這個script 將所有原始檔加上 line number 集中到一個檔案中,再印出來,方便 檢視。如果環境許可,甚至可貼在牆上那就更方便了。

    Wed Dec 3 11:37:16 CST 2025 Untitled Document
    流程控制 - for 迴圈
       
    如果 for 迴圈沒有 arg-list, 就會把所有位置變數當成是 arg-list。
    for var    #相當於 for var in $* 
    do                     
        commands            
    done                   
    
     
    範例

    Script: echoall 執行結果
    for i    
    do    
        echo $i   
    done         
    
    %echoall xx yy zz
    xx
    yy
    zz
    
    Wed Dec 3 11:37:16 CST 2025 Untitled Document
    流程控制 - while/Until 迴圈
       
    'while' 及 'until' 是迴圈的最基本型態,每一個迴圈都要檢視進入迴圈 的條件,彈性最大,但也比較麻煩,也比較容易發生錯誤。'while' 是在進入迴圈之前檢視進入條件,而 'until'則反之,是在執行完一個迴圈 才檢視。 此外,while 是在條件為真時執行迴圈,而until 是在條件 為假時執行迴圈。其格式如下:
    while condition-command 
    do              
        commands     
    done            
    
    until condition-command 
    do              
        commands     
    done            
    

     
    範例

    Script: while-example 執行結果
    number=0 
    while [ $number -lt 10 ]
      do                       
        echo "$number\c"      
        number=`expr $number + 1`   #將變數 number 加一 
    done                         
    echo                         
    
     0123456789
    
    Wed Dec 3 11:37:17 CST 2025 Untitled Document
    流程控制 - break and continue
       
    這兩者是用於for, while, until 等迴圈控制下。break 會跳至done後方執行 ,而continue會跳至done執行,繼續執行迴圈。
       
    範例
    while 
       echo "Please enter data ('done' to exit):"
       read response
    do
       if [ "$response" = "done" ]
       then 
          break 
       fi
       if [ "$response" = "" ]
       then 
          continue 
       fi
       _......
       _......
       process data
       _......
       _......
    done
    
    Wed Dec 3 11:37:17 CST 2025 Untitled Document
    Function ( 函式 )
    函式 (Function)
    Wed Dec 3 11:37:17 CST 2025 Untitled Document
    Function ( 函數/函式 )
       
    幾乎所有的高階程式語言都必須提供副程式的功能,讓程式設計者可以將重複性的 任務寫成副程式,俾便重複使用,既能節省空間,程式也比較清爽,有助於對邏輯的 分析與理解。副程式在不同的程式語言中,有不同的名稱,在 C 與 Shell 中,叫做 函數或函式 (Function)、在物件導向(Objet-Oriented)程式中,稱為方法 (Method)。
       
    Shell 有提供 函式 功能,也有recursion 功能, 但是處理變數較為麻煩,本書的宗旨在寫出短小精悍的小程式, 盡量避免耗時費力使用recursion 功能,有興趣鑽研這個功能的讀者,請自行研讀。
    定義函式
       
    1. 先需定義函式,才能使用
    2. 首先定義函式名稱,後接括弧,而括弧 '()' 中必須是空的。
    3. 使用 return [n]' 敘明離開狀態, 相當於 'exit [n]'。
    4. 若無 return statement, 最後執行的指令的狀態 (Exit Status) 就作為整個函式的 exit status.
    定義函式的格式如下:
      function-name()
      {
          commands
      }
    
    呼叫函式 (Invoke Function)
       
    呼叫函式時,就像在命令列下直接下命令一般,例如:
    myfunc()
    {
    echo Input are: $1 $2 $3
    }
    myfunc 10 20 30 
    
    結果螢幕上會印出 10 20 30 出來。
    Input are: 10 20 30 
    
    Man Page of 'return' Wed Dec 3 11:37:17 CST 2025 Untitled Document
    函式之執行
       
    除了下列兩種情況之外,函式 (Function) 是在 current shell 下執行,而非啟動另一個 subshell 來執行
    • 使用了I/O 轉向
    • 被使用在 backquote (``)中
     
    指令或函式在不同Shell層級執行對環境的影響
       
    指令在不同Shell層級執行對環境有著不同的影響,如下:
    被影響的環境 在 current shell 執行 在 subshell 執行
    工作目錄 (Current directory)
    可能被改變
    不變
    變數
    可能被改變
    不變
    在函式中執行 Exit 指令
    結束函式及
    原呼叫程序
    並跳出
    單純結束函式
    Wed Dec 3 11:37:17 CST 2025 Untitled Document
    函式參數的傳遞
     
    利用位置變數傳遞參數
       
    Shell 在呼叫函式時,如何將參數傳給函式? 第一種方式:就是利用位置變數 (position variable) 即可,在呼叫時,就像使用一般指令一般,函式名稱在前,其他參數 逐一接上成為引數,而在函式內部則用位置變數取用。原先的 呼叫方所擁有的位置變數除了$0之外在函式中暫時不能使用, 當函式結束時,原先的位置變數會恢復原值可以使用。
     
    利用一般使用者變數傳遞參數
       
    第二種方式則是利用一般使用者變數傳遞參數,換言之,呼叫函式的 Shell 所擁有的使用者變數,在函式中都可以用得到,此因函式仍然是 current shell 在執行,使用者變數在同一個程序(process)中是 全域變數(global variable),變數若在函式中被改變, 那呼叫者原來的值會被蓋掉,不被保留。如果要避免同名干擾,可將 函式中的同名變數設為local,就能將該變數變成函式內的區域變數, 與呼叫者所擁有的變數區隔開來。
     
    Shell 函式具有 recursion 功能(危險!)
       
    Shell 函式雖然具有 recursion 功能,但是變數名稱的衝突 卻須使用者自行解決,非常繁瑣而危險,不建議讀者使用 recursive 功能。
    Wed Dec 3 11:37:17 CST 2025 Untitled Document
    函式之回傳值
       
    第一種方式:藉由使用者變數直接回傳。
       
    第二種方式:可讓 函式將輸出送到 STDOUT, 而 parent shell 則用 command substitution 捕捉之。例如:
    square ( ) {
       expr "$1" \* "$1" 
    }
    x=$(square "$1") 
    y=`square "$1"`
    
    Wed Dec 3 11:37:18 CST 2025 Untitled Document
    函式之範例
    函式運用之例:
    #-------------------------------------
    # Script: wait_for --- wait for a user to log in
    # 用法:  wait_for user [ sleeptime ]  
    #------------------------------------- 
    wait_for ( ) {
       until who | grep "$1" > /dev/null
       do
          sleep ${2:-100}
       done
    }
    wait_for clinton    #Wait for clinton, check every 100 sec 
    wait_for obama 60 #Wait for obama, check every 60 sec

    equal ( ) {
       test  "$1" -eq "$2" && return 0 || return 1 
    }
    equal "$a" "$b"  && echo "$a and $b are equal"
    equal "$c" "$d"  || echo "$c and $d are not equal"
    
    Wed Dec 3 11:37:18 CST 2025 Untitled Document
    特殊檔案: /dev/null and /dev/tty
     
    Shell 提供幾個特殊的虛擬檔案, /dev/null 及 /dev/tty。
    /dev/null ("bit bucket")
       
    如果想要將 STDOUT 的東西捨棄直接丟進垃圾桶的話,可利用 I/O轉向將STDOUT 轉向到一個虛擬的檔案: /dev/null。
    範例
    #-------------------------------------
    #測試看一個檔案是否含有某些字串
    #------------------------------------- 
    if grep pattern myfile > /dev/null
    then
      commands for true (Pattern is there)
    else
      commands for false (Pattern is not there)
    fi
    
    /dev/tty
       
    這個虛擬檔案代表終端機,STDOUT的預設值是轉向到此, 所以輸出到 STDOUT 的資料都會出現到終端機上,若STDOUT 被 轉向到一個真正的檔案時,終端機的螢幕上就不會出現輸出的資訊。 如此,一個指令或script 的設計者並不知道所設計的指令最終是輸出 到何處去,而是下指令的使用者決定。萬一程式設計者希望某個輸出是 一定到終端機螢幕,而不會被轉向,(例如:送出一個訊息到螢幕,請使用 者回答 passoword),在這種情形下,script 內部 可指定輸出轉向到 /dev/tty,就可達到目的。
    printf "Enter new password: " #Prompt for input
    stty -echo #Turn off echoing of typed characters
    read pass < /dev/tty #Read password
    printf "Enter again: " #Prompt again
    read pass2 < /dev/tty #Read again for verification
    stty echo #turn echoing back on
    ...
    Wed Dec 3 11:37:18 CST 2025 Untitled Document
    設定變數的預設值 (Default Values in Variable Expansion)
       
    當一個尚未定義的變數被使用時,若無其他設定,其值將將預設為空字串, 有時候會對shell script 的執行產生阻礙。 POSIX 標準讓程式設計師可以事先設定遇到此種情況時的應對方式, 可以防止這種情形發生。兩種格式如下:
    ${var[-=?+]default}
    
    ${var:[-=?+]default}
    冒號(:)在設定預設值時的意義如下:
    有冒號 未定義或含有空字串的變數都會觸發設定的應對
    沒有冒號 僅有未定義的變數會觸發設定的應對
    符號 應對方式 例 (Colon) 例 (No Colon)
    - 變數var如未定義,則回傳預設值default
      變數 var 維持不變
    echo ${var:-default} echo ${var-default}
    + 變數var如有定義,則傳回 default 值,
      var 之內容維持不變
    echo ${var:+default} echo ${var+default}
    = 變數var如未定義,則回傳預設值default
      並將變數 var 賦值為 default
    echo ${var:=default} echo ${var=default}
    ? 變數var如未定義,則印出Message,
      而 Shell 程序則終止執行
    echo ${var:?message} echo ${var?message}
    範例
    %echo ${var}
     
    %echo ${var:-Null}
    Null
    %echo ${var:+notNull}
     
    %echo ${var:=toset}
    toset
    %echo ${var}
    toset
    %var=LoveYou
    %echo ${var}
    LoveYou
    %echo ${var:-Null}
    LoveYou
    %echo ${var:+notNull}
    notNull
    %echo ${var:=toset}
    LoveYou
    Wed Dec 3 11:37:18 CST 2025 Untitled Document
    變數展開的POSIX擴充功能-字串與算數運算
       
    原始的 Bourne Shell 並沒有內建數值運算與字串的處理功能, 只能依靠外部指令來完成數值運算與字串處理,非常不方便,效率也差, 有鑑於此,POSIX 特地在其標準中將變數展開這個功能加以擴充, 加入有限的數值運算與字串處理能力。

    語法 功能
    ${var:offset:length} 擷取字串中之片段 (extract sub-string)
    ${var/pattern/string} 代換字串中之片段(substition)
    ${#var} 計算字串之長度
    ${var#pattern} 刪除字串之單一前綴(delete single prefix)
    ${var##pattern} 刪除字串之最長前綴(delete longest prefix)
    ${var%pattern} 刪除字串之單一後綴(delete single postfix)
    ${var%%pattern} 刪除字串之最長後綴(delete longest postfix)
    ${${!prefix*}
    ${!prefix@}
    列出變數名稱
    $((expression)) 算數運算
    Wed Dec 3 11:37:19 CST 2025 Untitled Document
    變數展開的字串處理功能
    字串串接
       
    在 Shell 中,將兩個字串黏在一起(concatenation)是 很簡單的,例如:
     $var1$var2     
    #------------------------------------------- 
    #將 $var1 及 $var2 展開後,黏在一起。
    
    但是其他的字串處理,就很無能為力了,必須依靠 sed 等指令來處理字串,例如:
    a=`echo $var | sed 's/nccu/NCCU/'`      
    #----------------------------------------------------------------
    #將變數$var的內容展開交給sed將裡面的字串nccu改成NCCU,然後賦值於變數$a 
    
    
    擷取字串中之片段   ${var:offset:length}
     
    方法一: 用指令 'cut'
    範例
    %s=0123456789
    %echo $s | cut -c3-6 #擷取第3-6字元
    2345
     
    方法二: 用POSIX 變數展開之擴充功能 ${var:offset:length}
       
    先展開變數 $var,將展開的字串從offset 開始截取長度為length 的片段,頭尾去除。 length 若從缺,則截到到字串的尾端。
    範例
    %s=0123456789
    %echo ${s:2:4}
    2345
    %echo ${s:2:10}
    23456789
    %echo ${s:2}
    23456789
    代換字串中之片段   ${var/pattern/string}
     
    方法一: 用 'sed'
    範例
    %s=0123456789
    %echo $s | sed 's/012/abc/'
    abc3456789
     
    方法二: 用POSIX 變數展開之擴充功能 ${var/pattern/string}
       
    先展開變數 $var,將展開的字串跟'pattern'比對, 若有符合,則用'string'取代所有符合的部分。 #--------------------------------------------
    #註: 檔案名稱之萬用字元在'pattern' 中是起作用的。
    範例
    %s=0123456789
    %echo ${s/012/abc}
    abc3456789
    %echo ${s/0?2/abc}
    abc3456789
    %echo ${s/0*4/abc}
    abc56789
       
    'pattern' 的開頭如果有下列符號,則有特殊意義:
    / 所有符合 'pattern'的字串都被取代 ${var//pattern/string}
    # 'pattern' 只有在字串之開頭才符合 ${var/#pattern/string}
    % 'pattern' 只有在字串之後頭才符合 ${var/%pattern/string}
    範例
    %s=abcabcABC
    %echo ${s//abc/123}  #echo $s| sed 's/abc/123/g'
    123123ABC
    %echo ${s/#abc/123}  #echo $s| sed 's/^abc/123/'
    123abcABC
    %echo ${s/%abc/123}  #echo $s| sed 's/abc$/123/'
    abcabcABC
    %echo ${s/#ABC/123}  #echo $s| sed 's/^abc/123/'
    abcabcABC
    %echo ${s/%ABC/123}  #echo $s| sed 's/abc$/123/'
    abcabc123
    計算字串之長度   ${#var}
       
    範例
    %x=supercalifragilisticexpialidocious
    %echo There are ${#x} characters in $x
    There are 34 characters in supercalifragilisticexpialidocious
    刪除字串之單一前綴   ${var#pattern}
       
    先展開變數 $var,將展開的字串從前面跟'pattern'比對, 若有符合,則刪除第一個符合的部分, (如果 $var 不是以 'pattern' 開頭則不會進行任何動作)
    範例
    %var=abcde
    %echo ${var#ab}
    cde
    %echo ${var#bc}
    abcde
    %path=/a/b/long.file.name
    %echo ${path#/*/}
    b/long.file.name
    刪除字串之最長前綴   ${var##pattern}
       
    先展開變數 $var ,將展開的字串從前面跟'pattern'比對, 若有符合,則刪除所有符合的部分, (如果 $var 不是以 'pattern' 開頭則不會進行任何動作) 此功能可以用來取代 basename 中去除path name 的功能 也可以用來截取副檔名
    範例
    %var=ababcde
    %echo ${var#ab}
    abcde
    %echo ${var##ab}
    cde
    %echo ${var##bc}
    ababcde
    %path=/a/b/long.file.name
    %echo ${path#/*/}
    b/long.file.name
    %echo ${path##/*/}
    long.file.name
    刪除字串之單一後綴   ${var%pattern}
       
    先展開變數 $var ,將展開的字串從後面跟'pattern'比對, 若有符合,則刪除第一個符合的部分, (如果 $var 不是 'pattern' 在字串之最後不會進行任何動作)。 此功能可以用來取代 basename。
    範例
    %var=abcde
    %echo ${var%de}
    abc
    %echo ${var%bc}
    abcde
    %path=/a/b/long.file.txt
    %echo ${path%.*}
    /a/b/long.file
    刪除字串之最長後綴   ${var%%pattern}
       
    展開變數 $var ,將展開的字串從後面跟'pattern'比對, 若有符合,則刪除所有符合的部分, (如果 $var 不是 'pattern' 在字串之最後不會進行任何動作)。
    範例
    %var=abcdede
    %echo ${var%de}
    abcde
    %echo ${var%%de}
    abc
    %path=/a/b/long.file.txt
    %echo ${path%.*}
    /a/b/long.file
    %echo ${path%%.*}
    /a/b/long
    列出變數名稱   ${!prefix*}, ${!prefix@}
       
    將所有變數中以'prefix'開頭的變數名稱列出來,
    '*'所有列出的變數是一個字串,
    '@'每一個列出的變數是一個字串。
    範例
    %a1=1
    %a2=2
    %a3=3
    %echo ${!a*}
    a1 a2 a3
    Wed Dec 3 11:37:19 CST 2025 Untitled Document
    檔案名稱的字串處理
       
    Shell programming 常常需要處理檔案名稱, 例如: cc 這個編譯器在編譯一個 C 檔案,如 example.c時,會產生中間檔案'example.o',所以編譯器的執行過程中, 須將'example.c'去掉副檔名將字根'example'從檔案名稱中抽取出來, 再貼上'.o'字串,最終產生一個'example.o'的字串作為中間檔名。 檔案的轉換,例如 'file.txt' 轉成 CSV 檔時,要產生一個新黨名 'file.csv' 也需要將字串 'file' 自 'file.txt' 中抽取出來。 有時候需要將路徑抽取出來,或截斷。我們以 '/a/b/file.txt'為例, 歸納幾個常見的檔名處理需求:
    需求說明 結果
    擷取副檔名 txt
    裁掉副檔名 /a/b/file
    擷取目錄路徑 /a/b
    裁掉目錄路徑 file.txt
    裁掉副檔名及目錄路徑 file
    而很多Unix版本都會提供一個很有用的指令 'basename' 可用來將檔案名稱去掉副檔名。 與 'basename' 類似的,'dirname' 可用來將檔案名稱中的檔名去除而留下目錄的路徑名稱。 配合 sed 的字串編輯功能,就能輕易滿足上述需求。如果系統 具備POSIX的擴充功能,則更為簡單。
     
    擷取副檔名
    Script getext_v1
    s=/a/b/file.txt
    echo $s | sed 's/..*\.//'
    
    Script getext_v2
    s=/a/b/file.txt
    echo ${s##*.}
    
     
    裁掉副檔名
    Script cutext_v1
    #assume extension name is known
    #---------------------------
    s=/a/b/file.txt
    echo $s | sed 's/.txt$//'
    
    Script cutext_v2
    s=/a/b/file.txt
    echo ${s%.*}
    
     
    擷取目錄(資料夾)路徑
    Script getpath_v1
    s=/a/b/file.txt
    echo `dirname $s`
    
    Script getpath_v2
    s=/a/b/file.txt
    echo ${s%/*}
    
     
    裁掉目錄路徑
    Script cutpath_v1
    s=/a/b/file.txt
    basename $s
    
    Script cutpath_v2
    s=/a/b/file.txt
    echo ${s%%/*/}
    
     
    裁掉副檔名及目錄路徑
    Script getroot_v1
    #assume extension name is known
    #---------------------------
    s=/a/b/file.txt
    basename $s .txt  
    
    Script cutroot_v2
    s=/a/b/file.txt
    t=${s%%/*/}
    echo ${t%/.*}
    
    Wed Dec 3 11:37:19 CST 2025 Untitled Document
    變數展開的算數計算功能 (Arithmetic Expansion)
       
    數學運算式的符號如下:
    $((expression))
    運算式(expression)的語法與C 語言的運算式相同。 其中,對於特殊符號而言,雙括弧有如double quote ("" '") 一般而言, 除了 「"」之外的特殊符號不須另外再用"\" 來escape。
    運算子 意義 結合律方向
    ++ --
    Increment and decrement,
    prefix and postfix
    L2R
    + - ! ~
    Unary plus and minus;
    logical and bitwise negation
    R2L
    * / %
    Multiplication, division,
    and remainder
    L2R
    + -
    Addition and subtraction
    L2R
    << >>
    Bit-shift left and right
    L2R
    < <= > >=
    Comparisons
    L2R
    == !=
    Equal and not equal
    L2R
    &
    Bitwise AND
    L2R
    ^
    Bitwise Exclusive OR
    L2R
    |
    Bitwise OR
    L2R
    &&
    Logical AND
    L2R
    ||
    Logical OR
    L2R
    ?:
    Conditional expression
    R2L
    = += -= *=
    /= %= &= ^=
    <<= >>= |=
    Assignment operators
    R2L
       
    括弧的作用與 C 語言中的括弧是一樣的。 例如:
    運算式 結果
    $((3 > 2)) 1
    $(( (3 > 2) | | (4 <= 1))) 1
       
    在邏輯運算中,非零的數值等於「真」,例如:
    $ echo $((3 && 4))      #Both 3 and 4 are "true" 
    #-----------------------------------------
    結果:  1 
    
    範例
     $i=5
     $j=3
     $echo $((i+j)) 
      8
     $echo $((i++)) $i
      5 6
     $echo $((++i)) $i
      7 7
    
    Wed Dec 3 11:37:20 CST 2025 Untitled Document
    特殊符號 (Special Characters)
       
    除了文字型態的關鍵詞之外,Shell 還定義了一些特殊符號,

    SP
    Tab
    NL
    =
    ;
    &
    )
    (
    >
    <
    |
    ^
    {
    }
    $
    ;
    #
    *
    ?
    [
    ]
    `
    \
    '
    "
    Wed Dec 3 11:37:20 CST 2025 Untitled Document
    特殊符號的Escape (Remove Meaning of Special Characters)
    在字串中使用特殊符號
       
    字串中如果含有特殊符號,必須保護(escape)起來,否則Shell 會誤解而啟動相應的特殊動作。共有三種保護機制:
     \ 
       ' '  
       "  "  
     
    1. Backslash ('\')
    .
    Backslash '\' 保護單一的符號
    # ---------------------
    # example  single'\' 
    # ---------------------- 
    full=Joe\ Smith
    在一行的末尾接一個'\'
    可將次行連接起來,
    合併成同一行。
    # ---------------------
    # example '\' before NL
    # ---------------------- 
    echo this is a \ test
    兩個連續的 Backslash '\\'
    # ---------------------
    # example '\\'
    # ---------------------- 
    echo \\ is one backslash
     
    2. 單引號(' ')
       
    所有特殊符號均失去特殊意義。
     
    3. 雙引號(" ")
       
    除了下列四個符號外,其他的特殊符號均失去特殊意義。
    $
    `
    "
    \
    範例
    %x=this is a string
    is: not found [No such file or directory]
    %x="this is a string"
    %echo $x
    this is a string
    %echo "$x"
    this is a string
    %echo '$x'
    $x
    %echo "*"
    *
    %echo '*'
    *
    Wed Dec 3 11:37:20 CST 2025 Untitled Document
    雙引號(" ")之使用
       
    當字串中間含有須展開之變數或指令代換時,用雙引號比 單引號更為方便,例如下列兩行 echo 程式碼效果相同:
    # -------------------------------------
    # single quote vs double quote
    # ------------------------------------- 
    name=Clinton echo 'My name is '$name' and today is '`date` # -------------------------------------
    echo "My name is $name and today is `date`"
       
    此外,當一個字串裡面含有變數時,大大提高了不確定性,程式設計師 並不一定知道變數將來會充什麼樣的值進來,可能造成危險,下面 的 shell script 就非常危險:
    rm -rf /$var    #如果 $var 是空字串,會有什麼後果?
    
     
    'test' 指令的陷阱
    Script 安全性
    test "Clinton" = "Clinton" 安全
    test Clinton = Clinton 安全
    test "Bill Clinton" = "Bill Clinton" 安全
    test Bill Clinton = "Bill Clinton"
    test "" = "Clinton" 安全
    test   = "Clinton"

    安全
    string=""
    test $string = Clinton
    
    string=""
    test '$string' = Clinton
    
    string=""
    test "$string" = Clinton
    

    安全
    string="Bill Clinton"
    test $string = Clinton
    
    string="Bill Clinton"
    test '$string' = Clinton
    
    string="Bill Clinton"
    test "$string" = Clinton
    
    Wed Dec 3 11:37:20 CST 2025 Untitled Document
    選項的處理 (Option Processing)
       
    一個功能強大的指令通常會有許多option 讓使用者選擇,否則便要製造出很多 不同版本的指令,大大的增加維護的成本及使用者的記憶負擔。而 option 可能 非常靈活而有不同的組合,但處理起來便非常麻煩。以下的例子利用 case/if 等方式 硬生生的處理一個複雜的 option 組合。
    file=  
    verbose=  
    quiet=  
    long=
    while [ $# -gt 0 ]   #Loop until no args left 
    do
       case $1 in   #Check first arg 
       -f) file=$2
          shift   #Shift off "-f" so that shift at end gets value in $2 
          ;;
       -v) verbose=true; quiet=  ;;
       -q) quiet=true; verbose=  ;;
       -l) long=true             ;;
       --) shift   #By convention, - - ends options 
           break
           ;;
       -*) echo $0: $1: unrecognized option >&2 ;;
        *) break  ;;    # Nonoption argument, break while loop 
       esac
       shift   #Set up for next iteration 
    done
    
    Wed Dec 3 11:37:20 CST 2025 Untitled Document
    選項的處理 -getopts
       
    為了節省處理 Option 的麻煩,POSIX 將option 的格式統一做了規範 並提供了一個 getopts 的指令。大部分Unix 的使用者對於在 Shell 控制下,在指令後面下 option 的格式,應該都有了一定的瞭解,

    每一個option 都是單一字元
    數個option 可以合在一起變成一個字串
    每一個option 或每一組合在一起的 option 前面都有個 "-" 字元
    一個 option 後面可以帶有參數
    用 "--" 代表option 的結束
    其他引數接在 option 後面

    'getopts' 指令可協助一個 shell script 剖析 option,提供不少方便。 程式設計師可用一個迴圈將 option 逐個讀進來交給 'getopts' 處理。 'getopts'這個指令跟其他指令最大不同點是,它保有「記憶」。其他所有 指令每次執行都是獨立的,除了跟動態資訊(例如時間)有關的指令之外, 只要執行條件相同,得到的結果都必定相同,不會跟執行順序有關,但 'getopts' 在一個迴圈中執行時,每一次執行都有不同的結果,所以它 可以記住上一次執行到哪一個 option,這是跟其他的指令大為不同之處。 'getopts' 需要兩個參數:

    第一個參數敘明所有的 option。(所有option 的字元合併成一個字串) option 後面如需要參數,則在該字元後面加一個冒號(':')。例如下例中 的 'f:vql'。
    第二個參數是一個變數,用來記錄每次剖析使用者所給的 option 的結果

    'getopts' 在執行時,為產生兩個變數, $OPTIND 記錄目前剖析到第幾個 option,而$OPTARG 則記錄option 後面所帶的參數。以下是一個使用 'getopts'的例子:
     
    # -----------------------------------------------
    # getops v1
    # ----------------------------------------------- 
    file= verbose= quiet= long= while getopts f:vql opt do case $opt in #Check option letter f) file=$OPTARG ;; v) verbose=true; quiet= ;; q) quiet=true; verbose= ;; l) long=true ;; esac done shift $((OPTIND - 1)) # Remove options, leave arguments

       
    使用者所給的option 之前的'-' 符號自動被除掉
    如有'--'符號,也自動被除掉
    如果使用者給了一個不存在的符號當option, 'getopts' 或自動產生錯誤訊息。但程式設計師可選擇自行處理,只要 在'getopts' 的第一個參數之最前面加上冒號(':'),getopts 就不會產生錯誤訊息,而照常剖析之。
    # -----------------------------------------------
    # getopts v2 : 忽略錯誤訊息的選項處理
    # ----------------------------------------------- 
    file= verbose= quiet= long= while getopts :f:vql opt do case $opt in Check option letter f) file=$OPTARG v) verbose=true; quiet= ;; q) quiet=true; verbose= ;; l) long=true; ;; '?') echo "$0: invalid option -$OPTARG" >&2 echo "Usage: $0 [-f file] [-vql] [files ...]" >&2 exit 1 ;; esac done shift $((OPTIND - 1)) Remove options, leave arguments

    Man Page of 'getopts'

    Wed Dec 3 11:37:21 CST 2025 Untitled Document
    群組指令 (Command Group)
       
    設計師可以將數個指令組成一個群組,當作一個指令來做,格式如下:
     
      { command-list; }    #在 current process 執行 
     
      ( command-list )    #在 subshell 執行 
       
    群組指令在 I/O 轉向時非常有用,可以將一群指令的輸出用單一個 I/O轉向完成之。
     
    { date; ls } > filelist
    # -------------------------------
    { ...; ... } < inputfile
    # -------------------------------
    command | { ...; ... } 
    
       
    這兩種方式對執行環境的影響各有不同 :
    Script 執行結果對環境的影響
    { cd x; rm junk }
    
    執行完畢後,shell 停留在目錄 x
     
    ( cd x; rm junk )
    
    執行完畢後,shell停留在原目錄
    Wed Dec 3 11:37:21 CST 2025 Untitled Document
    怠工指令 (Null Command)
       
    一個怠工指令 (Null),不執行任何動作,傳回 "ture",格式如下:
     
      : [parameters ...]
    
    'parameter' 可以是變數展開或指令展開等,但對主程式無影響, 有興趣的讀者可以研究 看看有何有趣的用途。
     
    範例

    whileloop v1 whileloop v2 reverse_if
     
    while     : do
      commands
      if  exit_condition
      then
         break
      fi
    done
    
     
    while   true do
      commands
      if  exit_condition
      then
         break
      fi
    done
    
     
    if command-list
    then
         :
    else
       commands
    fi
    done
    

    #這個 script 把 IF 指令的測試條件顛倒,
    #有時候為了程式的邏輯比較清楚,
    #程式設計師會這樣寫程式。
    Wed Dec 3 11:37:21 CST 2025 Untitled Document
    程式的除錯工具:set
    set -v 列印進度訊息 (verbose)
    set -x 列印執行軌跡
    set - 關閉 -v, -x
    Wed Dec 3 11:37:21 CST 2025 Untitled Document
    指令 eval
       
    如果要把一個變數裡的內容展開後當成指令來執行,可用指令 eval。
     
    範例
    Without eval With eval
     
    y=CGI
    x='$y'
    echo $x
    
     
    y=CGI
    x='$y'
    eval echo $x 
    
    結果: $y 結果: CGI
    Wed Dec 3 11:37:22 CST 2025 Untitled Document
    複雜的I/O轉向
    一些常見的I/O轉向需求
    將錯誤訊息(STDERR)儲存到一個檔案
    command 2> errfile
    
    將 STDOUT 及 STDERR 分別儲存到兩個檔案
    command > stdout 2> stderr
    command > stdout 2>> stderr
    
    將 STDOUT 及 STDERR 存到同一個檔案
    command > stdout 2>&1
    
    將 STDOUT 儲存到一個檔案,捨棄 STDERR
     
    command 1> stdout 2> /dev/null 
    
    將 STDOUT 及 STDERR 同時送給 '|'
     
    command  2>&1 | nextcommand
    
    利用 /dev/tty 將 STDOUT 分流
       
    在設計一個具有與使用者互動能力的script 時,經常會 遇見一個難題: 如果使用者要將正常輸出訊息轉向到一個檔案去, 但仍然希望保留與 script 的對話於螢幕上不被轉向,我們 可將對話轉向到 /dev/tty。
    下面的script 可將 script 跟使用者的對話及script 的運算結果都輸出到 STDOUT 上,如果要存到檔案內, 只能一併轉向,無法分開。
    echo 請輸入姓名:
    read name
    echo 請輸入電話號碼:
    read phone
    echo 請輸入地址:
    read address
    echo "$name, $phone, $address"
    
    這樣的 script 對使用者很不方便,因為如果將輸出轉向到檔案 後,使用者無法跟script 對話。
       
    如果要將 script 跟使用者的對話留在螢幕上,不想被運算輸出一併被轉向 到檔案,可將不想被轉向的訊息都送到 /dev/tty 去,就不會被轉向了。 下面是一個例子:
    echo 請輸入姓名:   > /dev/tty 
    read name
    echo 請輸入電話號碼:   > /dev/tty 
    read phone
    echo 請輸入地址:   > /dev/tty 
    read address
    echo "$name, $phone, $address"
    
    如此,兩種不同的輸出都顯示在螢幕上,但只有送到STDOUT 的會被 轉向,而送到 /dev/tty 的訊息仍然會留在螢幕上。 使用者仍然 可以跟 script 繼續對話。
    Wed Dec 3 11:37:22 CST 2025 Untitled Document
    STDIN 轉向 及 Here
       
    當一個程式需要從鍵盤輸入資訊, 但不想麻煩使用者跟系統作互動性輸入時, 可用I/O轉向法將STDIN 轉向一個檔案,亦即轉從檔案讀進資料。 資料處理的任務經常會用到這個功能,從鍵盤輸入資料的程式,改由 事先準備好的資料檔案餵進程式。例如:
    data-processing-command < inputfile
    
    儲存編輯指令於檔案
       
    除了資料處理之外,有些情境也可使用 STDIN轉向,例如 編輯器。編輯一個文件的動作,原是由使用者鍵入編輯指令, 其實可以將編輯指令存放於一個檔案,然後用 STDIN轉向 餵給編輯器來編輯。例如,如果要將一批HTML 檔案內的某一個 名詞改掉,我們可用此法配合迴圈批次的執行任務。例如 下面的 ex 編輯指令存於 ex.script 內及執行程式。
    #editor script file: ex.script
    
    1,$s/TSMC/tsmc/g w
    #edit a batch of HTML files using loop and editing script
    
    for i in *.htm do ex $i < ex.script done
    注意,可視性編輯器例如'vi'並不適用此法,因為 vi 必須依賴螢幕的游標才能正確的編輯。
    儲存編輯指令及主程式於單一檔案
       
    上面的方法需要用到兩個檔案,有時並不利於管理, 下面的 script 將兩個檔案整合成一個檔案:
    echo '1,$s/TSMC/tsmc/g
    w' > ex.script
    for i in *.htm
    do
       ex $i < ex.script
    done
    
    利用Here儲存編輯指令及主程式於單一檔案
       
    for i in  *.htm
    do
    ex $i<<%
    1,\$s/TSMC/tsmc/g
    w
    %
    done
    
    Wed Dec 3 11:37:22 CST 2025 Untitled Document
    Signal and Trap
    Unix 信號 (Signal)
       
    傳統電腦架構的核心是CPU,當一個程序(Process)在佔用 CPU 執行時,不會半途主動停下。在多工的作業系統下,各個程序輪流使用 CPU,由作業系統主持輪班的作業,而作業系統可趁著輪班時 送信號 (Signal)給一個程序中斷之。例如 Unix 系統下,使用者 按下'^C'試圖中斷一個script 的執行程序,作業系統就會送一個 信號給該程序中斷之。
    常用的 Unix Signal
    Signal Name 說明
    0
    EXIT
    終止程序
    1
    HUP
    STDOUT被停止,程序要終止
    2
    INT
    鍵盤上的<Del>終止鍵被按下,程序要終止
    3
    QUIT
    鍵盤上的 '^C'終止鍵被按下,程序要終止,core dump
    9
    KILL
    強迫終止程序,trap 失效
    11
    SEGV
    記憶體衝突,立刻終止程序,trap 失效
    15
    TERM
    終止程序
    Trap
       
    一個優秀的程序設計師在設計一個程式時,除了要一些 防呆設計以防止使用者的錯誤使用造成系統的受損之外, 通常在程式的最後面有一些收尾工作需要執行, 例如,將所產生的中間檔案刪除,以免佔用空間, 也把目錄弄得很混亂。可是當一個程序接到作業系統送來的信號時, 必須立刻終止程序,交還 CPU 控制權, 便無法按照正常情況執行收尾的動作,就像建築界的爛尾樓。 為了避免這種問題,Shell 提供一個 trap 指令 給程式設計師使用,trap 會攔截作業系統的信號,交給 shell script,而程式設計師就可以預先設定所欲 攔截的信號以及相應的處理工作。在實際執行 script 時若遇到作業系統送來的信號,就能啟動相關的收尾工作。
       
    Trap 格式如下:
    trap [ command-list ] signo ...
    
    若無 command-list,則沒有對應的動作,等於是忽略所攔截的信號
    範例
    trap 'rm -f /tmp/myscript.$$; exit 1' 1 2 3 15
    
    接到信號 1 2 3 15 時將 /tmp/myscript.$$ 檔案刪除
    必須使用 '' 將指令保內的特殊符號護起來,避免一開始就被展開
    Shell 將這個指令儲存起來,等signal 到時,才拿出來執行
    執行時,特殊符號已經沒有''保護,會被shell 展開
    Wed Dec 3 11:37:22 CST 2025 Untitled Document
    nohup 程序的免死金牌與強迫終止
     
    終止一個程序之法
    終止前景執行的程序 在鍵盤上按下'^C'
    終止背景執行的程序 kill process-id
     
    程序的免死金牌
       
    有兩種方法可以讓一個程序免於被中斷。
    1. 使用trap 攔截終止信號,並忽略之。
    2. 使用 nohup 指令於程序之執行。
      nohup script &
      
     
    程序的強迫終止
       
    kill -9 process-id
    
    這個指令會送一個9號信號給 process-id 強迫終止它, 此時程序的免死金牌失效。
    Wed Dec 3 11:37:23 CST 2025 Untitled Document
    Links to other sources

    Linux Shell Scripting: Tutorial A Beginner's handbook
    A Bourne Shell Tutorial
    The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
    A Good Script Collection Wed Dec 3 11:37:23 CST 2025