Shell_Programming

Power of Unix Shell Programming

前言

Scripting Languages V.S. Compiled Languages

Unix 之優缺點

學習 Unix Shell Programing 之方法

Shell Script 設計小秘訣

將文字檔設為可執行

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

指令及參數 (Commands and Arguments)

檢視執行軌跡 (Execution Tracing)

指令之執行 (Command Execution)

Script的基本結構及觀念

變數

使用者變數

使用者變數命名規則

系統變數(環境變數)

POSIX Built-In Shell Environment Variables

Env

Unset

唯讀的使用者變數

特殊變數

POSIX Built-In Shell Special Variables

Accessing Shell Script Arguments - Position Parameters

Shift

Shift 有什麼用?

Simple Output with echo

printf

Read (從 STDIN 輸入)

算數運算 (Arithmetic Expression)

執行一個程式

命令替換 (Command Substitution)

I/O轉向與管線 (I/O Redirection and Pipe)

I/O Redirection

Avoid File From Truncated by I/O Redirection

Pipe

流程控制

Target of Condition Test

POSIX Defined Exit Status

Test

流程控制 - if

流程控制 - If 之例

流程控制 - If 之例

流程控制 - for 迴圈

流程控制 - for

流程控制 - while 迴圈

流程控制 - break and continue

流程控制 - Case

Function ( 函數 )

Function ( 函數/函式 )

Function - Function Execution

Function - Passing Parameter and Variables

Function- Return Value

Function - Examples

/dev/null and /dev/tty

Links to other sources


Untitled Document
Power of Unix Shell Programming
     

      Automate Your Unix Tasks      

 

 
by Arnold Robbins & Nelson Beebe in Classic Shell Programming
Thu Sep 7 16:18:49 CST 2023 Untitled Document
前言
批次檔
   
當你在電腦上從事一些例行的重覆性工作,最希望能寫成批次檔,讓批次檔 自動執行,可省下很多人力。早在早期的讀卡機時代,IBM 電腦就有 JCL (Job Control Language) 讓電腦操作員,控制電腦任務的自動執行, 雖然功能很陽春,但不失為一個批次執行的雛形。微軟的 DOS, 也有執行批次檔的能力,以 .bat 為延伸檔名的檔案就是批次執行檔。 在Unix 中,Shell 不但可以跟使用者互動,也可將所要執行的指令放在檔案內,讓 Shell 當作批次執行檔執行。就程式語言的觀點而言, 其能力已經和一般的高階語言不相上下。 批次檔在Unix中稱為Shell Script。
 
Shell scripts 是 ASCII 檔
   
Shell 都是以 ASCII 格式寫就,而 Shell 對二進位的執行檔與 ASCII 格式的 Shell script 一視同仁,都是當作執行檔來執行,但是對於二進位執行檔是直接 交付作業系統執行,但如果對於 shell script, 則由Shell 本身來執行,其實將一個 shell script 細部動作拆開來看,就能發現, 隨著shell script 的執行,一個個二進位執行檔被抓出來交給作業系統去 執行。 Shell 本身就是一個 程序 (Process)負責將一個 shell script 的內容轉成 一道道的命令執行。
   
Shell 的語法有許多不同的版本,不同語法寫就的 script 互不相容, 差異,所以我們不能將寫給 用 Shell A 語法寫出的 shell script 不能用 Shell B 來執行。比較風行的版本是原始版的 Bourne Shell, Korn Shell, Bash, 以及C Shell,蘋果電腦的 Mac OS X中的 Terminal 支援 Bash.
Thu Sep 7 16:18:50 CST 2023 Untitled Document
Scripting Languages V.S. Compiled Languages
   
Shell 是一種 Interpreter, 亦即不經過編譯(compiling)而一面剖析 script 一面執行。其效率雖然較低,但用在處理繁瑣的系統管理雜事,所費 CPU 時間並不多,但可省下使用者很多的人工時間。
Thu Sep 7 16:18:50 CST 2023 Untitled Document
Unix 之優缺點
Thu Sep 7 16:18:50 CST 2023 Untitled Document
學習 Unix Shell Programing 之方法
Thu Sep 7 16:18:50 CST 2023 Untitled Document
Shell Script 設計小秘訣
Thu Sep 7 16:18:50 CST 2023 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
所有人都不可以執行
Thu Sep 7 16:18:50 CST 2023 Untitled Document
Shell如何執行機械碼執行檔與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
Thu Sep 7 16:18:50 CST 2023 Untitled Document
指令及參數 (Commands and Arguments)
指令及參數
   
一個 shell script無論是以檔案的方式執行還是從終端機的鍵盤逐行敲進指令, 差異不大,前者是呼叫一個shell子程序 (subshell)來執行,而後者則由 login shell 執行。 事實上,從使用者login 之後,一直到 logout,中間所敲進的所有指令,合起來,就是一個shell script,這也是 為何 Unix 最原始的 logout 方式並非下一個指令,而是敲進一個end-of-file 的符號 ^D。以下是使用 者與Shell 互動的一個例子:
#-------------------------------------
#   假設 $ 是系統的提示符號
#------------------------------------- 
$ cd Work     #使用者敲進 cd Word 指令,要求切換目錄 
$ ls          #使用者敲進 ls 指令,要求列出目錄下所有檔案 
a1.c          #系統印出第一個檔案 
a1.o          #系統印出第二個檔案 
a.out         #系統印出第三個檔案 
$wc -l a1.c   #使用者敲進 wc 指令,要求執行 wc 指令 
 2027    5807   43932 1.c   #系統回應
   
指令格式很簡單:
  • 一行指令的各個元素之間用空白隔開。
  • 第一元素是指令名稱
  • 隨後可能跟著一個或數個 options, 首個字元是 '-' 符號,option 通常是單個字元,而數個option 可合併在一起由一個單一的'-'帶領,當然 option 也可以逐一分開。
  • 後面接著任何數量的參數 (Argument)
以下是一些各種型態的 ls 指令例子:
ls
ls mycode.*
ls -l 
ls -l  mycode.*
ls -la  
ls -l -a  
以上的格式並非強制性的,某些元老級的指令,例如 tar 的option 之前就省略了 '-'符號。 使用者在設計程式時,雖然可以任意設計成不同的格式, 但最好依循既有的格式。
指令間隔及背景執行符號
;
數個指令可放在同一行,其間以分號分開,Shell將逐行依序執行。
&
如果放一個 '&' 在指令之最後,Shell 將會將指令放在背景執行
Thu Sep 7 16:18:50 CST 2023 Untitled Document
檢視執行軌跡 (Execution Tracing)
檢視執行軌跡
   
如欲檢視Shell在執行一個 shell script 期間的詳細步驟,尤其是 變數的變化,有兩種方式為之:
  1. 下指令時利用 '-x' option 直接呼叫
    $ sh -x script   #呼叫 /bin/sh 來執行 
    + who            #系統印出執行細節 
    + wc -l          #系統印出執行細節 
    
  2. 下一個 "set -x" 指令,或放在script 中
    set -x   #turn on trace 
    set +x   #turn off trace 
    
Thu Sep 7 16:18:50 CST 2023 Untitled Document
指令之執行 (Command Execution)
 
指令之種類
   
作為一個完整的程式語言, Shell 本身自然有內建指令,諸如迴圈控制、變數以及函數等 常見指令,而其他如ls, cat 等常用指令都不是Shell的內建指令, 本書稱之為外部指令以別於內建指令。 外部指令其實就是一個個事先寫好,由系統提供或使用者 自行提供的機械碼執行檔,Shell 內建的指令大部分是用來安排外部指令的 執行順序,真正的工作,都是外部指令在做的。Shell好像將軍而外部指令是 士兵,將軍負責發出指令指揮士兵遂行作戰任務。我們常見的Shell指令, 例如: ls, cat, grep 等都是外部指令,有興趣的讀者可到 /bin 及 /usr/bin 等相關目錄下,可以看到這些指令的機械碼執行檔。
 
外部指令之執行步驟 以及 如何找到外部指令
1. 啟動一個新的子程序,執行這個任務。
2. 在新的子程序中,從變數 $PATH 中所列出的目錄中搜索使用者所要求的 外部指令執行檔。
3. 在新的子程序中,執行所得到的執行檔。
Thu Sep 7 16:18:51 CST 2023 Untitled Document
Script的基本結構及觀念
   
最簡單的 shell script 是將要執行的指令放在 script 內以批次方式執行
   
Script是以行為單位,我們所寫的script會被分解成一行一行來執行。 而每一行可以是指令、註解、或是流程控制指令等。 如果某一行尚未完成,可以在行末加上"\" , 這個時候下一行的內容就會接到這一行的後面,成為同一行,如下:
  echo The message is \
  too long so that it has to \
  be split into several lines 
   
當Script中出現"#" 時,再它後面的同一行文字即為註解, Shell 不會對其解譯,例如:
# this is a comment
# comment line 2
# comment line 3
   
Script的流程控制和一般高階語言的流程控制沒有什麼兩樣, 也和高階語言一樣有變數及副程式。這些使得shell script的功能更加強大。
Thu Sep 7 16:18:51 CST 2023 Untitled Document
變數
   
原始Bourne Shell的變數型態只有「字串」,可歸類成下列幾種:

使用者變數
唯讀的使用者變數
系統變數(環境變數)
特殊變數
   
使用set 指令可列出一個程序所定義的變數:
set
Thu Sep 7 16:18:51 CST 2023 Untitled Document
使用者變數
使用者變數
 
用'='設定變數
var=string
注意:'=' 左右兩邊不可有空白:
 正確: $num=10
 錯誤 $ num =10
 錯誤 $ num= 10
 錯誤 $ num = 10
可以指定 Null 到一個變數:
$ tech=
$ tech=""
 
取用變數的方法:
在變數名稱前加上一"$" 號
例: var-example   執行結果
 x=15
   echo x
   echo $x
   echo ${x}
 x
 15
 15
Thu Sep 7 16:18:51 CST 2023 Untitled Document
使用者變數命名規則
使用者變數命名規則
第一個字符必須是英文字母或 "_"
第二個以後的字符可以是任意序列的Alphanumeric字元(英文字母、數字)及 "_"
英文字母是 Case Sensitive,有區分大小寫
總長度由各系統自主決定,沒有統一規定
   
合格之 Shell 變數名稱之例:

HOME
SYSTEM_VERSION
tech
Myfile2
_pay2me_

   
不合格之 Shell 變數之例:
acct!
2ndfile
$owed
   
軟體工程建議了許多變數命名的規則,讀者請自行參考。
Thu Sep 7 16:18:51 CST 2023 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' Thu Sep 7 16:18:51 CST 2023 Untitled Document
POSIX Built-In Shell Environment Variables
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 指令的搜尋路徑
Thu Sep 7 16:18:51 CST 2023 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. Thu Sep 7 16:18:51 CST 2023 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.
Thu Sep 7 16:18:51 CST 2023 Untitled Document
唯讀的使用者變數
 
唯讀變數
   
唯讀變數和使用者變數相似,只不過這些變數不能被改變。 只要加上 readonly 關鍵詞就可將使用者變數設成唯讀:
readonly var
注意:系統變數不可以設定成唯讀的。

readonly 後面如果沒有接變數名稱,則會列出所有唯讀的變數。

例: readonly-var-example 執行結果
name1=Clinton
echo $name1
readonly name1
name2=John
echo $name2
readonly name2
Clinton
John
name1=Clinton
name2=John
Thu Sep 7 16:18:52 CST 2023 Untitled Document
特殊變數
   
有些變數是一開始執行Script時就會設定,並且不可以加以修改, 此種變數稱為「特殊變數」(有些書稱為「唯讀的系統變數」)。
$0
記錄這個 script 的執行名字 (通常是script的檔案名稱)
$d
位置變數,記錄這個 script 的第d個位置參數,d 是數字
$*
記錄這個 script 的所有位置參數,展開後成為一個字串
$@
記錄這個 script 的所有位置參數,展開後每一個位置參數成為一個獨立字串
$#
記錄這個 script 的位置參數個數
$$
記錄這個 script 的PID (Process ID)
$!
記錄執行上一個背景指令的PID (Process ID)
$?
記錄執行上一個指令的返回值
Thu Sep 7 16:18:52 CST 2023 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.
Thu Sep 7 16:18:52 CST 2023 Untitled Document
Accessing Shell Script Arguments - Position Parameters
位置參數(Position Parameters) 及 位置變數(Position Variables)
   
當使用者下一個指令時,後面經常會給一些參數,稱為位置參數 (Position Parameters),例如輸入資料的檔案名稱等。而 shell script 必須能取得這些參數,憑以執行使用者交付的任務。 Shell 會主動取得這些參數,然後放在幾個特殊系統變數內,稱為 「位置變數」 (Position Variable)內,對應如下:
指令 command arg1 arg2 ...... ...... arg9
Shell Script 內部 $0 $1 $2 .... .... $9
其中 $0 是指令名稱,如是外部指令,則為執行檔之檔案名稱。

例: position-var-example
echo first arg is $1
echo tenth arg is ${10} 
for i in $1 $2 $3 $4 $5
do
grep "^$i" /etc/passwd
done

 
特殊變數 $* 是一個字串存有所有的 position variables
for i  in $*
do
grep "^$i" /etc/passwd
done
for i 
do
grep "^$i" /etc/passwd
done
   
有關 for 迴圈的介紹請參見相關章節

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

Man Page of 'set'

Thu Sep 7 16:18:52 CST 2023 Untitled Document
Shift
Shift
   
我們可以使用shift 命令將 position variables 往前移一格,捨棄前幾個,後面的變數則往前遞補,shift 可帶有參數,讓設計師指定移動數個參數。
 
Shift [n]

例: shift-example 執行: (shift-example 20 30 40 50)
結果
# ----------------------------
# 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
Thu Sep 7 16:18:52 CST 2023 Untitled Document
Shift 有什麼用?
   
初學Shell Programming 的人可能用不上 shift 這個指令,但如果 要設計一個不定數量的位置參數的程式時,這是一個必要的指令。
   
為了讓一個 script 可以重覆使用,script 的設計通常不會將輸入參數寫死在 script 內,而是讓使用者在呼叫 script 時再利用位置參數給定, 就像系統所提供的指令大多是如此設計。 位置參數的數量有時候不是固定的,有多有少, 例如 'cat' 這個指令就能接受任意數量的位置參數如下;
cat 1.txt *.csv
難題一: 如何處理不定數量的位置參數?
   
這類 script 可以用如下的形式逐一處理輸入檔案:
for i in   $* 
do
  do-something $i    #do-something 是一指令,取用一個位置參數 
done
上面這個script中,'$*'展開後,會將所有的位置參數納入, 一舉解決位置參數的數量不固定的難題。 但是如果位置參數的前幾個與後面的參數 有不同的意義時,上面的 script 就不適合了。
難題二: 同時有固定數量及不定數量的位置參數
   
假設script的設計很特殊,第一個 位置參數是一個txt 檔,而第二個參數以後是 不定數量的 csv 檔, 而script被要求對不同的 類的檔案需要不同的處理方式, 這時就用得上 shift 了。
  #本程式讀取第一個txt檔,將之後的所有csv 檔內的逗號改成空白,全部輸出到 STDOUT
#=========================================================================

cat $1  
shift   #捨棄第一個位置參數 
for i in $* do sed 's/,/ /g' $i #利用sed將逗號改成空白
done
Thu Sep 7 16:18:52 CST 2023 Untitled Document
Simple Output with echo
echo
 
echo 是最常用的 shell script 輸出指令。 echo 將後面所帶的字串印出到 STDOUT,echo 其實是一隻 外部程式,將位置參數一一列出,中間以一個空白隔開, 最後印出一個「換行」的符號。 例如:
$ echo This is a    book.
#-------------------------------------
結果: This is a book.
   
有時候在設計 shell script 時,不希望 echo 在最後印出一個 「換行」的符號,要讓游標停留在該行的尾巴, 接下來,無論是 使用者鍵入任何字串,或script 再印出東西來,都會接在游標後面, 而不會跳到新的一行。(相當於在C 語言的printf 中的參數後面沒有加上 "\n")
   
可惜POSIX 標準並沒有定義這個。 System V 提供一個特殊的控制符號 "\c", 當使用者交付給 echo 的參數後面,帶有"\c" 時,echo 就不會印出「換行」。有些版本的 Shell 則在 echo 提供一個option "-n" , 當使用者在下 echo 指令時 如果帶有 -n option 時,也能達到同樣的效果。

#-----------------------------------
     Some version of Shell
#-----------------------------------
$ echo -n "Enter your name: " 
#-------------------------------------
結果: Enter your name: _ Enter data
 
within the arguments.

#-----------------------------------
     System V version 
#-----------------------------------
$ echo "Enter your name: \c" Print prompt
#-------------------------------------
結果:  Enter your name: _ Enter data
 
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'   
Man Page of 'echo' Thu Sep 7 16:18:52 CST 2023 Untitled Document
printf
printf
   
Shell 現在也提供類似 C 語言中的 printf 指令,讓程式設計師可以指定輸出的格式:
#-----------------------------------------------
$ printf "This script prints '%s, %s!'\n" Hello world
#-----------------------------------------------
#結果: This script prints 'Hello, world!'
Man Page of 'printf' Thu Sep 7 16:18:52 CST 2023 Untitled Document
Read (從 STDIN 輸入)
read
   
類似 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 
#----------------------------- 
do something
echo Is Printer ready?
   read anything 
lp xxxxxxx
Thu Sep 7 16:18:53 CST 2023 Untitled Document
算數運算 (Arithmetic Expression)
   
Shell 的變數都是字串型態,因此非常不利於算數運算, 因此,Shell 並不適合作為大量算數運算的程式語言。 原始版本的 Bourne Shell 並無 內建的運算式,(我們將在後面的章節介紹PPOSIX定義的運算式), 如果真的有需要處理數值運算,我們可以使用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"
   
Generate numbers from 1 to 1000
# -----------------------------------------------
# counter1000: generate numbers from 1 to 1000
# ----------------------------------------------- 
count=1 while [ $count -le 1000 ] do echo $count count=`expr $count + 1 ` done
Thu Sep 7 16:18:53 CST 2023 Untitled Document
執行一個程式
   
在Bourne Shell中使用者有五種方法請 Shell 執行一個執行檔(機械碼執行檔,或一個 script) , 而這五種方式所產生的結果有些許的不同。
 
1. 直接鍵入執行檔檔名。
(執行檔須先設定可執行權限,執行檔所在的目錄必須記錄在 $PATH 之內。)
   
myscript  
呼叫一個子程序 (subshell) 執行 myscript
 
2. 使用sh命令,
將檔案名稱作為位置參數鍵入
(執行檔不須設定可執行權限,但必須設定為可讀)
   
sh myscript  
呼叫一個子程序 (subshell) 執行 myscript
 
3. 使用"."命令
(執行檔不須設定可執行權限,但必須設定為可讀)
   
  • myscript    
  • 這時和使用sh命令相似,只不過它不像sh一般會產生新的子程序,相反的, 它會在原有的程序下完成工作。 它會在原有的程序下完成工作。 在這種執行方式下,一個script 可以改變原有程序的環境變數 內容。前者方式則無法改變。
     
    4. 使用exec命令
    (執行檔不須設定可執行權限,但必須設定為可讀)
       
    exec script  
    
    此時這個 Shell 將會被所執行的命令所取代。當這個命令執行完畢之後,這個 Shell也會隨之結束。
     
    5. 使用命令替換
    (Command Substitution)
    (執行檔須先設定可執行權限,執行檔所在的目錄必須記錄在 $PATH 之內。)
       
    `script`  
    
    這是一個相當有用的方法。如果想要使某個命令的輸出成為另一個命令的參數 時,就一定要使用這個方法。我們將命令列於兩個"`" 號之間,而 Shell 會以這個命令執行後的輸出結果代替這個命令以及兩個"`" 符號。 (須先設定可執行權限)
    Thu Sep 7 16:18:53 CST 2023 Untitled Document
    命令替換 (Command Substitution)
    Command Substitution
       
    在C 語言內,執行一個函數 (Function)時,Function 所傳回的值會被程式抓住直接使用。 而在 shell script 中,各外部指令可能會將執行結果輸出到 STDOUT,如果要將指令的執行結果抓回script內運用時,要如何做?
       
    Shell 內提供數種方法:
    例: command-substitution v1 執行結果
    echo "Current directory is   `pwd` "
    

    echo "Current directory is   $(pwd) "
    
    Current directory is /usr/lien
    
    註: pwd 這個命令輸出"/usr/lien",
    而後整個字串代替原來的`pwd`,
    填入字串,用來設定str 變數。

     
    更多例子:
    today=`date`
    echo "Today is $today"
    
    echo "Today is `date`"
    
    echo "Today is $(date)"
    
    number=`expr $number + 1`
    
    number=$((number++))
    
    #----------------------
    # 印出目錄下所有檔案
    #---------------------- 
    for i in  `ls` 
    do
    echo "==== File Name: $i ===="
    cat $i
    done
    
    Thu Sep 7 16:18:53 CST 2023 Untitled Document
    I/O轉向與管線 (I/O Redirection and Pipe)
    Thu Sep 7 16:18:53 CST 2023 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
    Thu Sep 7 16:18:53 CST 2023 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
    Thu Sep 7 16:18:53 CST 2023 Untitled Document
    Pipe
    Thu Sep 7 16:18:53 CST 2023 Untitled Document
    流程控制

    流程控制
    Thu Sep 7 16:18:53 CST 2023 Untitled Document
    Target of Condition Test
    條件式的測試標的物
       
    一般程式語言一定有條件式用來控制流程,而最常見的測試是比對兩個數值、 兩個字串或兩個物件,而比對條件則是「相等」、「大於」、「小於」 、「不等」等。 作為一個程式語言,Shell 當然也提供數值與字串的比較。 但是,Shell 的強項並非數值運算,而是檔案與程序的處理。最需要下列相關測試: 檔案是否存在?讀寫權限為何?子程序的執行是否成功?等等。 Shell 提供一個內建指令,test,作為種種測試之用。(為了符合一般的使用習慣,測試指令 可用方括弧 ' [ 測試條件 ]' 取代)。 測試的條件以 'test' 指令的 option 的型態給定。 'test' 指令的執行成功與否,提供給 流程控制使用。
       
    簡單的說,一個指令執行如果是成功的,其回傳的 Status 為0, 否則為其他數值。而條件式的測試標的物,就是回傳的 Status。
       
    以下是可以一看即知的簡單實例:
    data1=4
    data2=6
    if [ $data1 -eq 4 ]
    then 
       echo "Data1 is 4"
    fi
    if test $data2 -ne 6
    then 
       echo "Data2 is not 6"
    fi
    
    上面的script 可以改寫成比較簡潔美觀的 script 如下:
    data1=4
    data2=6
    [ $data1 -eq 4 ] && echo "Data1 is 4"
    [ $data2 -eq 6 ] || echo "Data2 is not 6"
    
    其中,'&&'這個符號的意思是 前面指令執行的結果如果是成功的,就執行後面的指令。 而'||'這個符號的意思是 前面指令執行的結果如果是失敗的,就執行後面的指令。 這樣的寫法,更簡單清楚,更容易分析與維護, 在後面的章節中會再解釋。
       
    使用者在撰寫一個 script 時,是可以利用 'exit n' 將 Status 設定為 n。當然正常使用者應該不會在執行成功時,將 Status 設定為 0 以外的值。 舉例如下:
    echo "Success"
    exit 0
    
    echo "Failure"
    exit 4
    
    Man Page of 'exit' Thu Sep 7 16:18:54 CST 2023 Untitled Document
    POSIX Defined Exit Status
    Value Meaning
    0 Command exited successfully.
    > 0 Failure during redirection or word expansion (tilde, variable, command, and arithmetic expansions, as well as word splitting).
    1-125 Command exited unsuccessfully. The meanings of particular exit values are defined by each individual command.
    126 Command found, but file was not executable.
    127 Command not found.
    128 unspecified by POSIX
    > 128 Command died due to receiving a signal.
    Thu Sep 7 16:18:54 CST 2023 Untitled Document
    Test
       
    Shell 提供一個內建的 test 指令,作為字串比對、數值比較,檔案性質 測試之用,格式如下:
    test test-expression
    很多版本的 Shell 可用 中刮號 "[ ]" 將條件式刮起來,取代 test 指令。
    [ test-expression ]
    6/3/1986 Ksh 可用 [[ ]]
    [[ test-expression ]]

     
    單項測試 (Single Test)

    字串比對(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 檔案為非空的一般檔
     
    複合測試 (Multiple Tests)
       
    test-expression中可包含一個以上的判斷準則以作為測試條件。 兩準則間用 "-a" 代表邏輯 AND 運算,而 "-o" 代表邏輯 OR 運算, 而在準則前放置一個 "!" 符號則代表 NOT 運算。如 果沒有括號,則優先權順序為則為
    "!" > "-a" > "-o"
    如果有刮號時,由內而外優先執行刮號內的 test expression, 注意在 Shell 中,刮號也可作為將幾個指令組成群組的功能, 如此會產生混淆,因此在 test expreession 中的刮號必須加上 escape 符號 "\" 避免衝突。
     
    以下是兩個相同的例子:
    test -r "$filename" -a -s "$filename"
    
    [ -r "$filename" -a -s "$filename" ]
    
    Man Page of 'test' Thu Sep 7 16:18:54 CST 2023 Untitled Document
    流程控制 - if
       
    第一個流程控制是 "if",學過程式語言的讀者,應該很熟悉,不需特殊說明, 讀者應可瞭解其用法,以下是各種格式:
     
    if then
    if command
    then       
        then-commands 
    fi                
    
    if test condition
    then       
        then-commands 
    fi                
    
    if [ condition ]
    then       
        then-commands 
    fi                
    
     
    else
    if [ condition ]
    then       
        then-commands 
    else           
        else-commands
    fi
    
     
    elif
    			 
    if [ condition1 ]
    then        
        commands1 
    elif [ condition2 ] 
    then           
        commands2   
    else         
        commands3
        commands3
    fi
    
    Thu Sep 7 16:18:54 CST 2023 Untitled Document
    流程控制 - If 之例
       
    以下是一個三個版本的同一例子:
    功能
    測試使用者在執行這個 script 是否有加任何 position variable,如有,則印出來。
    用法
    chkarg [anything]
    Script XX
    #-------------------------------------
    # Script: chkarg v1
    #------------------------------------- 
    if [  $# -ne 0 ] 
    then           
        echo Arg: $* 
    fi                
    

    #-------------------------------------
    # Script: chkarg v2
    #------------------------------------- 
    if test $# -ne 0
    then           
        echo Arg: $* 
    fi                
    

    #-------------------------------------
    # Script: chkarg v3
    #------------------------------------- 
    [  $# -ne 0 ]  &&  echo Arg: $* 
    

    執行結果
    $ chkarg Hello
    Arg1: Hello
    $ chkarg
    $
    
    $* 是所有的 postion variables
    Thu Sep 7 16:18:54 CST 2023 Untitled Document
    流程控制 - If 之例
    功能
    跟使用者互動,引導使用者輸入資料,並比較之
    Script XX
    #-------------------------------------
    # Script:    if_example
    #------------------------------------- 
    echo 'word 1: \c'   # 印出訊息,等候使用者輸入 
    read word1          # 將使用者的輸入放入變數 word1 
    echo 'word 2: \c'
    read word2
    echo 'word 3: \c'
    read word3
    if [  "$word1" = "$word2" -a "$word2" = "$word3" ]
      then
        echo 'Match: words 1, 2, & 3'
    elif [ "$word1" = "$word2" ]
      then
        echo 'Match: words 1 & 2'
    elif [ "$word1" = "$word3" ]
      then
        echo 'Match: words 1 & 3'
    elif [ "$word2" = "$word3" ]
      then
        echo 'Match: words 2 & 3'
    else
        echo 'No match'
    fi
    
    某些版本的 Shell 所提供的 echo 的內容中,\c 是一個特殊符號,讓該行印出於螢幕後,游標停留於該行,不要跳至次行。 不同的版本可能有不同的方式達到同樣效果。
    Thu Sep 7 16:18:54 CST 2023 Untitled Document
    流程控制 - for 迴圈
       
    'for'迴圈是另個重要的流程控制。其格式如下:
    for var in arg-list 
    do               
        commands      
    done             
    
    var 是迴圈變數,每一圈執行時,會從 arg-list 中逐次取用一個代入迴圈變數中

    例 for_example 執行結果
    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
    用法
    concatgefile file-list
    Script XX
    #-------------------------------------
    # Script: concatefile v1
    #------------------------------------- 
    for i in $*
    do
    echo "====== FILE NAME: $i  ========"
    cat -n $i   # -n 這個 option 會將讀入的資料加上 line number 
    done > outfile
    
       
    這樣的一個小script,就可以遠遠超過視窗系統的效率,節省很多時間。

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

    Thu Sep 7 16:18:54 CST 2023 Untitled Document
    流程控制 - for
       
    如果 for 迴圈沒有 arg-list, 就會把所有 position variable 當成是 arg-list。
    for var    #相當於 for var in $* 
    do                     
        commands            
    done                   
    

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

    例: while-example 執行結果
    number=0 
    while [ $number -lt 10 ]
      do                       
        echo "$number\c"      
        number=`expr $number + 1`   #將變數 number 加一 
    done                         
    echo                         
    
     0123456789
    
    Thu Sep 7 16:18:55 CST 2023 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
    
    Thu Sep 7 16:18:55 CST 2023 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)的字串如下:
      *
    任意字串,包括 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
    
    Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function ( 函數 )
    函數 (Function)
    Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function ( 函數/函式 )
       
    幾乎所有的高階程式語言都必須提供副程式的功能,讓程式設計者可以將重複性的 任務寫成副程式,俾便重複使用,既能節省空間,程式也比較清爽,有助於對邏輯的 分析與理解。副程式在不同的程式語言中,有不同的名稱,在 C 與 Shell 中,叫做 函數或函式 (Function)、在物件導向(Objet-Oriented)程式中,稱為方法 (Method)。
       
    原始的 Bourne Shell 中並無提供 Function 功能,但是因為可以很方便的呼叫外部指令, 所以外部指令也肩負著 副程式的功能。但是這樣的方式畢竟是美中不足的,一個 script 寫下來,可能需要佔用好多檔案,難以管理,要檢視 script 時要分開檢視很多檔案,也是很麻煩,再者外部指令是要啟動另一個shell 程序去執行,也會佔用太多系統資源。 有鑑於此,Korn Shell 特地加進了函數(Function)的功能,雖然很陽春,但是畢竟有了 副程式的樣子,解決了部分的問題。
       
    函數 (Function) 的格式很簡單,如下:
      function-name()
      {
          commands
      }
    
    先需定義,才能使用
    首先定義函數名稱,後接括弧,而括弧 '()' 中必須是空的。
    使用 return [n]' 敘明離開狀態, 相當於 'exit [n]'。
    若無 return statement, 最後執行的指令的狀態 (Exit Status) 就作為整個函數的 exit status.
     
    呼叫 (Invoke)
       
    呼叫函數時,就像在命令列下直接下命令一般,例如:
    myfunc()
    {
    echo Input are: $1 $2 $3
    }
    myfunc 10 20 30 
    
    結果螢幕上會印出 10 20 30 出來。
    Input are: 10 20 30 
    
    Man Page of 'return' Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function - Function Execution
       
    除了下列兩種情況之外,函數 (Function) 是在 current shell 下執行,而非啟動另一個 subshell 來執行
    • 使用了I/O 轉向
    • 被使用在 backquote (``)中
     
    指令或函數在不同Shell層級執行對環境的影響
       
    指令在不同Shell層級執行對環境有著不同的影響,如下:
    被影響的環境 在 current shell 執行 在 subshell 執行
    工作目錄 (Current directory)
    可能被改變
    不變
    變數
    可能被改變
    不變
    在函數中執行 Exit 指令
    結束函數及
    原呼叫程序
    並跳出
    單純結束函數
    Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function - Passing Parameter and Variables
    參數的傳遞
     
    利用 Position Variable 傳遞參數
       
    Shell 在呼叫函數時,如何將參數傳給函數? 第一種方式:就是利用Position variable 即可,在呼叫時,就像使用一般指令一般,函數名稱在前,其他參數 一一接上,而在函數內部則用 Position Variable 取用。原先的 Shell (parent shell) 所有的position variable 在函數中暫時不能使用,但$0 在函數中,維持為 parent shell 的名稱,仍然可以使用。等函數結束,原先的 position variable 會恢復原值可以使用。 (註 :某些系統可能會有不一樣的設計,parent shell 的 Position Variables 可能會被蓋掉而失去原來的值。
     
    利用一般使用者變數傳遞參數
       
    第二種方式則是利用一般使用者變數傳遞參數,換言之,parent shell 所擁有的使用者變數,在函數中都可以用得到,變數若在函數中被改變, 那 parent shell原來的值會被蓋掉,不被保留。 對使用者變數而言,函數的功能比較像 Macro 而不像傳統意義的函數。
       
    為了避免變數名稱的衝突,導致不測的邏輯錯誤,建議讀者保持一個 能避免衝突的變數名稱命名的習慣,例如: 所有在函數內定義的變數名稱, 都是以"_"開頭,而一般的script 則避免如此命名變數,就可以用人為方法 避開變數名稱之衝突。
     
    Shell 函數具有 recursive 功能(危險!)
       
    Shell 函數雖然具有 recursive 功能,但是變數名稱的衝突 卻須使用者自行解決,非常繁瑣而危險,不建議讀者使用 recursive 功能。
    Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function- Return Value
    回傳值
       
    第一種方式:藉由使用者變數直接回傳。
       
    第二種方式:可讓 函數將輸出送到 STDOUT, 而 parent shell 則用 command substitution 捕捉之。例如:
    square ( ) {
       expr "$1" \* "$1" 
    }
    x=$(square "$1") 
    y=`square "$1"`
    
    Thu Sep 7 16:18:55 CST 2023 Untitled Document
    Function - Examples
    函數運用之例:
    #-------------------------------------
    # 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"
    
    Thu Sep 7 16:18:55 CST 2023 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/null,就可達到目的。
    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
    ...
    Thu Sep 7 16:18:56 CST 2023 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 Thu Sep 7 16:18:56 CST 2023