在 Linux 中,shell 的功能是相當強大的,許多系統管理的功能都可以由 shell 來完成,跟其他程式語言相同,shell 也有其特定語法格式,分成以下幾個部分來說明。
Variables
變數定義的部分,必須注意到引號(單雙引號)的使用與影響,以下用範例來說明:
#!/bin/sh
myvar="Hi there"
echo $myvar
echo "$myvar"
echo '$myvar' #使用單引號就不會印出變數內容
echo $myvar #escape
echo Enter some text
read myvar
echo '$myvar' now equals $myvar
exit 0
# ========== 執行結果 ==========
# Hi there
# Hi there
# $myvar
# $myvar
# Enter some text
# Hello World! <== 這個部分是使用者輸入的
# $myvar now equals Hello World!
接著說明 Environment Variables,這個部分大多數是使用者個人的設定,以下列出幾個常用的環境變數:
Environment Variables | 描述 | 範例 |
$HOME | 使用者家目錄 | /home/godleon |
$PATH | PATH 設定(由冒號所隔開) | /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games |
$PS1 | 命令提示字元 | ${debian_chroot:+($debian_chroot)}\u@\h:\w\$ |
$PS2 | 命令提示字元 | > |
$IFS | 用來隔開輸入參數的字元,通常為 space、tab 或 newline | |
$0 | shell 的名稱 | -bash |
$# | 傳入參數的數量 | 2 |
$$ | shell script 所屬的 process ID | 5465 |
繼續是 Parameter Variables,其用途很明顯,當然是跟傳入的參數有關:
Parameter Variables | 描述 | 範例 |
$1, $2, ...... | 傳入的參數 | para1, para2, ..... |
$* | 所有傳入的參數,以 $IFS 隔開 | para1 para2 para3 (假設 $IFS 為一個 space) |
$@ | 所有傳入的參數,不過並非由 $IFS 隔開,而是用一個 space 作間隔 | para1 para2 para3 |
以下用一個範例說明 environment/parameter variables 的使用:
#/bin/bash
salutation="Hello"
echo $salutation
echo "The program $0 is now running" # $0 (shell script 名稱)
echo "The second parameter was $2" # $2 (第二個參數)
echo "The first parameter was $1" # $1 (第一個參數)
echo "The parameter list was $*" # 參數列表
echo "The user's home directory is $HOME" # 家目錄
echo "Please enter a new greeting"
read salutation # 讀取使用者的輸入
echo $salutation
echo "The script is now complete"
exit 0
# ========== 執行結果 ==========
# Hello
# The program try_var.sh is now running
# The second parameter was two
# The first parameter was one
# The parameter list was one two
# The user home directory is /home/godleon
# Please enter a new greeting
# Hello Bash Shell
# Hello Bash Shell
# The script is now complete
Conditions
在 shell script 中可以用 test 指令來測試命令執行後所產生的 exit code,因此在每一個 shell script 的最後一行,建議都加上 exit code 來回應程式執行的結果。
在 shell 中,目前有三種 condition type,分別列表如下:
String Comparison | 結果 |
str1 = str2 | True,若 str1 等於 str2 |
str1 != str2 | True,若 str1 不等於 str2 |
-n str | True,若 str 不為 null |
-z str | True,若 string 為 null 或空白字串 |
Arithmetic Comparison | 結果 |
exp1 -eq exp2 | True,若兩個結果相同 |
exp1 -ne exp2 | True,若兩個結果不相同 |
exp1 -gt exp2 | True,若 exp1 大於 exp2 |
exp1 -ge exp2 | True,若 exp1 大於等於 exp2 |
exp1 -lt exp2 | True,若 exp1 小於 exp2 |
exp1 -le exp2 | True,若 exp 小於等於 exp2 |
! exp | True,若 exp 為 false |
File Conditional | 結果 |
-d file | True,若檔案為目錄 |
-e file | True,若檔案存在 |
-f file | True,若檔案為一般檔案 |
-g file | True,若檔案有設定 set-gid |
-r file | True,若檔案可讀取 |
-s file | True,或檔案大小不為 0 |
-u file | True,若檔案有設定 set-uid |
-w file | True,若檔案可寫入 |
-x file | True,若檔案可執行 |
以下用一個簡單範例來說明使用方式:
#!/bin/bash
if [ -f /bin/bash ]
then
echo "file /bin/bash exists"
fi
if [ -d /bin/bash ]
then
echo "/bin/bash is a directory"
else
echo "/bin/bash is not a directory"
fi
# ========== 執行結果 ==========
# file /bin/bash exists
# /bin/bash is not a directory
Control Structures
shell 提供了許多不同的控制結構,以下分別一一介紹:
IF
#!/bin/bash
echo "Is it morning? Please answer yes or no"
read timeofday
if [ $timeofday = "yes" ]
then
echo "Good morning"
elif [ $timeofday = "no" ]; then # 請注意此處,必須加上分號以及另外一個 then 喔!
echo "Good afternoon"
else
echo "sorry, $timeofday not recongnized. Enter yes or no"
exit 1
fi
exit 0
FOR
這個部分需要注意一下,變數必須以 space 為間隔!
#!/bin/bash
# 會將 $(ls *sh) 的結果,以 space 為間隔印出
for file in $(ls *.sh);
do
echo $file
done
exit 0
WHILE
#!/bin/bash
echo "Enter password"
read trythis
while [ $trythis != "secret" ];
do
echo "sorry, try again"
read trythis
done
exit 0
UNTIL
#!/bin/bash
# 無使用者登入
until who | grep $1 > /dev/null
do
sleep 60
done
# 偵測到使用者登入
echo "$1 has just logged in"
exit 0
CASE
#!/bin/bash
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes)
echo "Good morning";; # 每個 statement 結束都必須用兩個分號
no)
echo "Good afternoon";;
y)
echo "Good morning";;
n)
echo "Good afternoon";;
*)
echo "sorry, answer not recongnized";;
esac
exit 0
#!/bin/bash
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | y | Yes | YES) # 可用 regular expression 一次篩選多個條件
echo "Good morning" # 可執行多個指令
echo "Ip bright and early this morning"
;; # 每個 statement 結束都必須用兩個分號
[nN]*)
echo "Good afternoon";;
*)
echo "sorry, answer not recongnized"
echo "Please answer yes or no"
exit 1
;;
esac
exit 0
LISTs
其實講白了,這個就是 AND & OR
#!/bin/bash
touch file_one
rm -f file_two
# AND 的用法
if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo " there"
then
echo "in if(and)"
else
# 由於 file_two 已經刪除,因此執行到此處
echo "in else(and)"
fi
# OR 的用法
if [ -f file_one ] || echo "hello" || echo " there"
then
# 執行到此處,因為 -f file_one 為 true
echo "in if(or)"
else
echo "in else(or)"
fi
exit 0
Functions
function 的呼叫,除了必須注意是否有回傳值之外,還要注意 global/local variables 的問題! 以下用範例來說明:
沒有回傳值的使用範例:
有回傳值的使用範例:#!/bin/bash
sample_text="global variable" # 全域變數
foo() {
local sample_text="local variable" # 定義為區域變數
echo "Function foo is executing"
echo $sample_text # 區域變數
}
echo "script starting"
echo $sample_text # 全域變數
foo
echo "script ended"
echo $sample_text # 全域變數
exit 0
#!/bin/bash
# function 的宣告中不會帶參數,必須在呼叫時帶入
yes_or_no() {
echo "Is your name $* ?" # 印出帶入的參數
while true
do
echo -n "Enter yes or no: "
read x
case $x in
y | yes)
return 0;; # 0 表示回傳 true
n | no)
return 1;; # 1 表示回傳 false
*)
echo "Answer yes or no"
esac
done
}
echo "Original parameter are $*"
if yes_or_no "$1" # 帶入第一個參數
then
echo "Hi $1, nice name"
else
echo "never mind"
fi
exit 0
Commands
break
用法其實跟其他程式語言是相同的,作為跳出回圈之用,而 continue 的用法也相同。
#!/bin/bash
rm -rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in fred*
do
if [ -d $file ]
then
break; # 效果同 C 語言中的 break
fi
done
echo "first directory starting fred was $file"
rm -rf fred*
exit 0
. (dot)
dot,透過 dot 會讓在 script 中的指令不會進入 sub shell 執行
# ========== dot.sh ==========
#!/bin/bash
PS2="###"
exit 0
# ========== 執行結果 ==========
# 原本的 $PS2
# shell> echo $PS2
# >
# 一般執行方式
# shell> sh dot.sh
# shell> echo $PS2 # 進入 sub shell 執行,因此還是沒有修改到 parent shell 中的設定
# >
# 以 dot 方式來執行
# shell> . dot.sh
# shell> echo $PS2
# ###
eval
將組合而成的字串變成有意義的變數
#!/bin/bash
foo=10
x=foo
y='$'$x
echo $y # 輸出 => $foo
# 使用 eval 來處理(類似 * 將 pointer 所代表的值提取出來的功能)
eval y='$'$x
echo $y # 輸出 => 10
export
在 sub shell 中定義環境變數之用
# ==================== export1.sh ====================
#!/bin/bash
foo="The first meta-syntactic variable"
export bar="The second meta-syntactic variable" # export 後即為環境變數
sh export2.sh
# ==================== export2.sh ====================
#!/bin/bash
echo $foo
echo $bar # 只有此行會顯示出內容,因為經過 export
expr
執行類似其他程式語言的基本數學運算處理
一般來說,在 shell programming 中是不能做數學運算的,因為 shell 把所有的值都視為字串(string),不過透過 expr 就可以解決這個問題,以下列出 expr 可以進行的簡單數學運算:
敘述 | 作用 |
expr1 | expr2 | 若 expr 不為 0,則回傳 expr1;反之回傳 expr2 |
expr1 & expr2 | 或兩者任何一個為 0,則回傳 0;反之回傳 expr1 |
expr1 = expr2 | 判斷是否相同 |
expr1 > expr2 | 判斷 expr1 是否大於 expr2 |
expr1 >= expr2 | 判斷 expr 是否大於等於 expr2 |
expr1 < expr2 | 派斷 expr1 是否小於 expr2 |
expr1 <= expr2 | 判斷 expr1 是否小於等於 expr2 |
expr1 != expr2 | 判斷 expr1 是否不等於 expr2 |
expr1 + expr2 | 回傳兩者相加的結果 |
expr1 - expr2 | 回傳兩者相減的結果 |
expr1 * expr2 | 回傳兩者相乘的結果 |
expr1 / expr2 | 回傳兩者相除的結果 |
expr1 % expr2 | 回傳餘數 |
接著用範例說明:
# x++
x = `expr $x + 1`
x = $(expr $x + 1)
printf
非常類似 C 語言中的 printf,但不支援浮點數的表示
shell> printf "%sn" Hello
Hello
shell> printf "%s %dt%s" "Hi there" 15 people
Hi there 15 people
set
在程式中設定參數
#!/bin/bash
echo the date is $(date)
# 設定參數(以 space 為區隔)
set $(date)
# 取第二個參數
echo The month is $2
exit 0
# ========== 執行結果 ==========
# shell> sh set.sh
# the date is 二 2月 26 15:10:00 CST 2008
# The month is 2月
shift
shift 參數之用,常用來瀏覽所有參數
#!/bin/bash
while [ "$1" != "" ]
do
echo $1
# $2->$1 $3->$2 $4->$3
shift
done
exit 0
# ========== 執行結果 ==========
# shell> sh shift.sh 1 2 3
# 1
# 2
# 3
trap
用來指定接收到特殊訊號時的處理動作
訊號(signal)的種類大致可分以下幾種:
訊號 | 描述 |
HUP (1) | Hang up (通常在 terminal 離線或使用者登出時會送出此類訊號) |
INT (2) | Interrupt (按下 Ctrl + C) |
QUIT (3) | Quit (按下 Ctrl + \) |
ABRT (6) | Abort (通常程式執行發生嚴重錯誤時會送出此類訊號) |
ALRM (14) | Alarm (通常用來處理逾時之用) |
TERM (15) | Terminate (通常當機器要關機時,由系統送出) |
#!/bin/bash
#若接收到 interrupt 的訊號,以下的命令就會執行
trap 'rm -f /tmp/my_tmp_file_$$' INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$
echo "press interrupt (Ctrl+C) to interrupt ...."
while [ -f /tmp/my_tmp_file_$$ ]
do
echo File exist
sleep 1
done
echo The file no longer exists
#即使接收到 interrupt 的訊號,檔案也不會被刪除
trap INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$
#因此此處會進入無限回圈
echo "press interrupt (Ctrl+C) to interrupt ...."
while [ -f /tmp/my_tmp_file_$$ ]
do
echo File exist
sleep 1
done
echo we never get here
exit 0
# ========== 執行結果 ==========
# shell> sh trap.sh
# creating file /tmp/my_tmp_file_10581
# press interrupt (Ctrl+C) to interrupt ....
# File exist
# File exist
# File exist <=== 此處按下 Ctrl + C
# The file no longer exists
# creating file /tmp/my_tmp_file_10581
# press interrupt (Ctrl+C) to interrupt ....
# File exist
# File exist
# File exist <=== 此處按下 Ctrl + C
unset
刪除自己設定的環境變數
#!/bin/bash
foo="Hello World"
echo $foo
# unset 後,foo 變數就不存在了
unset foo
echo $foo
# ========== 執行結果 ==========
# shell> sh unset.sh
# Hello World
# (此處為空白)
沒有留言:
張貼留言