2008年2月26日 星期二

Beginning Linux Programming 學習筆記(3) - Shell Programming (Shell Syntax)

前言

在 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 二 226 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
# (此處為空白)

沒有留言:

張貼留言