顯示具有 BASH 標籤的文章。 顯示所有文章
顯示具有 BASH 標籤的文章。 顯示所有文章

2009年1月31日 星期六

在 Windows 中編輯 Linux Shell Script 的問題

最近開始練習 Linux shell script,想說用 Windows 上的編輯器來編輯

卻發現程式一直都無法正確執行

後來網路上找到資料,原來是 Windows 與 Linux 的換行字元不同所導致.......

這時候就必須要修改一下編輯器的設定了!

以 MadEdit 為例:


將換行字元改為 Unix,程式就可以正常執行啦! 


參考資料

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

Beginning Linux Programming 學習筆記(2) - Shell Programming (Pipes & Redirection)

Redirect

在說明 redirect 之前,必須知道 redirect 可以處理的,僅有 file descriptor 0(standard input), 1(standard output), 2(standard error) 三種,接著用例子說明使用方式:
# 將 ls -al 所產生的訊息輸出至檔案 lsoutput.txt 中(檔案內容會被覆蓋)
shell> ls -al > lsoutput.txt

# 將 ls -al 所產生的訊息附加至檔案 lsoutput.txt 中
shell> ls -al >> lsoutput.txt

# 使用可能會產生錯誤訊息的指令(例如 kill)
# 將 standard output 輸出至檔案 killout.txt 中
# 將 standard error 輸出至檔案 killerr.txt 中

shell> kill -HUP 1234 >killout.txt 2>killerr.txt

# 將 standard output & error 輸出到同一個檔案中
shell> kill -1 1234 >killouterr.txt 2>&1

# 不保留任何 standard output & error 相關訊息
shell> kill -1 1234 >/dev/null 2>&1

# 將 mydata.txt 檔案中的內容透過 more 指令呈現 (standard input)
shell> more < mydata.txt


Pipes

pipe 是將前一個 process 處理的結果交給下一個 process 繼續進行處理,藉而達到類似連結多個 process 的效果。

在 Linux 中,連接 process 的數量並沒有限制,並且還能跟 redirect 來搭配同時使用,以下用幾個範例來說明:
# (1) 取得 ps 相關資訊
# (2) 排序後將結果存入檔案 pssort.txt 中

shell> ps | sort > pssort.txt

# (1) 取得 ps 相關資訊
# (2) 排序
# (3) 使用 more 來展示

shell> ps | sort | more

# (1) 取得 ps 相關資訊
# (2) 排序
# (3) 過濾掉重複的 process name
# (4) 清除名稱為 sh 的 process
# (5) 使用 more 來展示

shell> ps -xo comm | sort | uniq | grep -v sh | more

# (1) 使用 cat 讀取 mydata.txt 的內容
# (2) 將內容進行排序
# (3) 過濾掉重複的資料,並將結果存回 mydata.txt
中
shell> cat mydata.txt | sort | uniq > mydata.txt

2008年2月20日 星期三

Beginning Linux Programming 學習筆記(1) - Library 簡介

簡介

撰寫 C 語言時,除了必須用的 header file 之外,另外一個重要角色,就是 library 了! 而 library 又分為兩種:
  1. static library (副檔名為 .a)
  2. shared library (副檔名為 .so.N)


Static Library

所謂的 static library,其實沒很特別,只是一堆 object file 的集合而已,以下介紹 static library 的用法。

假設有兩個 function 分別位於不同的 file 中:
// ==================== fred.c ====================
#include <stdio.h>

void fred(int arg) {
printf("fred: we passed %d\n", arg);
} //end fred

// ==================== bill.c ====================
#include <stdio.h>

void bill(char *arg) {
printf("bill: we passed %s\n", arg);
} //end bill
另外設計一支程式,使用到上面所定義的 function:
// ==================== program.c ====================
#include <stdlib.h>

#include "lib.h"

int main(void) {
bill("Hello World!");
exit(0);
} //end main
接著是編譯的方式,有幾種方式可以進行編譯:
# (1) 直接將主程式與 function file 一同編譯
shell> gcc -o program program.c bill.c

# (1) 將定義兩個 function 的檔案編譯成 object file
# (2) 將主程式與 object file 一同進行編譯

shell> gcc -c fred.c bill.c ; gcc -o program program.c bill.o

# (1) 將定義兩個 function 的檔案編譯成 object file
# (2) 產生 static library (archive)
# (3) 將主程式與 static library 一同進行編譯

shell> gcc -c fred.c bill.c ; ar -crv libfoo.a fred.o bill.o ; gcc -o program program.c libfoo.a

# (1) 將定義兩個 function 的檔案編譯成 object file
# (2) 產生 static library (archive)
# (3) 將主程式與 static library 一同進行編譯(-L 指定 library directory、-l 指令所要用的 library name)

shell> gcc -c fred.c bill.c ; ar -crv libfoo.a fred.o bill.o ; gcc -o program program.c -L. -lfoo
其中第三種是比較標準的作法,不過其實 gcc 的功能很強,所以在編譯的時候可以省略一些指令卻可以達到相同效果。


Shared Library

談到 shared library 之前,必須先說明 static library 的缺點,就是當很多程式都使用相同的 static library 時,會在 memory 中產生多份相同的 copy,無形之中造成記憶體很多浪費,因此才會有 shared library 的出現。

shared library 存放的位置跟 static library 大致上是相同的,不過副檔名為 .so.N (N 為重新修改的次數)。

使用 shared library,即時同時被多個程式所使用,系統也只要在 memory 中存有一份資料即可,比起 static library 是節省記憶體許多。

而其運作的方式,在一開始時,shared library 是不會被載入 memory 中,而是當主程式執行時(此時主程式被載入 memory 中),將主程式所使參照使用到的 share library 在執行期間載入 memory 中執行。

接著,用來處理主程式與 shared library 參照工作的程式,稱為 ld.so(或是 ld-linux.so.N),而相關設定檔位於 /etc/ld.so.conf 中,若是設定有任何改變,則需要執行 ldconfig 指令重建參照的工作。

最後,若要查詢一個程式執行時所需求的 shared library,可使用以下指令:
shell> ldd program
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e89000) # 需求 libc.so 這個 shared library
/lib/ld-linux.so.2 (0xb7fdf000)

2007年6月17日 星期日

bash 學習筆記 - 流程控制

與其他程式語言相比,bash 當然有提供的流程控制的能力,分別是 if/else、for、case、select、while/until,以下分別介紹這些語法的使用方式。

if/else

if/else 的語法如下:
if condition
then
statements
[elif condition
then statements...]
[else
statements]
fi

Exit Status

與其他程式語言(C、C++、Pascal....等等)不同的地方,在 condition 的部分,其他的程式語言是以 condition 為 True or False 來決定要執行哪一段程式;但在 bash 中卻不是如此!

在 bash 中,就必須按照 Linux 的運作方式來進行處理,因此 condition 是否成立取決於 command 的 exit status。

而在 Linux 的所有指令,不論是由何種程式語言開發,執行完後都會回傳一個整數值給呼叫此程式的 process,此整數值即稱為 exit status;一般來說,「0」代表程式執行成功,而「1~255」則代表程式執行失敗。

有了 exit status 的概念後,if/else 的語法就可以修改如下:
if command ran successfully
then
normal processing
else
error processing
fi

Return

這邊要探討的是一般程式語言都有提供的 return 功能,其實說穿了,return N 即是 exit N(Exit Status 為N),不過比較不一樣的是,return 僅能用於 function 中,且要執行含有 function 的 shell script,必須透過 source 的方式來執行才有效。

總而言之,在 function 中使用 return 就跟在其他程式語言的方式差不多。

同時使用多個 Exit Status

或許有人會覺得,如果條件判斷中需要同時判斷兩個條件以上,要如何作呢? 在 bash 中當然也辦的到,就像其他程式語言一樣,bash 也可以針對判斷條件進行 &&(AND) 或是 ||(OR) 運算,來決定是否執行特定程式,以下用個簡單的範例說明:
filename=$1
word1=$2
word2=$3
if grep $word1 $filename || grep $word2 $filename
then
echo "$word1 or $word2 is in $filename."
fi

各種狀況的判斷

當然除了靠 Exit Status 外,還有許多不同的情況會發生,例如:字串比對、檔案屬性的檢查、數字比較....等等,以下逐一來進行介紹各種比對方式。

字串的比對:

Operator
成立條件
str1 = str2
這不多解釋了....
str1 != str2
這不多解釋了....
str1 < str2
這不多解釋了....
str1 > str2
這不多解釋了....
-n str1
當 str1 不為 null 時
-z str1
當 str1 為 null 時(長度為 0)

檔案屬性:
Operator
成立條件
-a file
檔案存在
-d file
檔案存在,並且是個目錄
-e file
檔案存在
-f file
檔案存在,且為一般檔案(非目錄或其他特殊型態)
-r file
目前的使用者有讀取檔案的權限
-s file
檔案存在且內容非空白
-w file
目前的使用者有寫入檔案的權限
-x file
目前的使用者有執行檔案(or 進入目錄)的權限
-N file
檔案自從上一次被讀取後,有被修改過
-O file
檔案的 owner 為目前的使用者
-G file
檔案的 group 為目前使用者所屬之 group
file1 -nt file2
file1 比 file2 的日期新
file1 -ot file2
file1 比 file2 的日期舊

如果使用上面的判斷時,最好加上「中括號」,變成類似以下的格式:
if [ condition ] && [ condition ]; then
當然也可以跟 Exit Status 結合,就會變成類似以下的格式:
if command && [ condition ]; then
以下來段簡單的程式(節錄自書):
# 檢查檔案是否不存在
if [ ! -e "$1" ]; then
echo "file $1 does not exist."
exit 1
fi

# 檢查是否為目錄
if [ -d "$1" ]; then
echo -n "$1 is a directory that you may "
if [ ! -x "$1" ]; then
echo -n "not "
fi
echo "search."
elif [ -f "$1" ]; then # 檢查是否為檔案
echo "$1 is a regular file."
else # 檢查是否為特殊型態的檔案
echo "$1 is a special type of file."
fi

# 檢查目前使用者是否為 owner
if [ -O "$1" ]; then
echo 'you own the file.'
else
echo 'you do not own the file.'
fi

# 檢查是否有讀取檔案的權限
if [ -r "$1" ]; then
echo 'you have read permission on the file.'
fi

# 檢查是否有寫入檔案的權限
if [ -w "$1" ]; then
echo 'you have write permission on the file.'
fi

# 檢查第一個參數是否有執行的權限,且非目錄
if [ -x "$1" -a ! -d "$1" ]; then
echo 'you have execute permission on the file.'
fi
當然,兩兩比較一定會有數字的比對,以下介紹數字比較的方式:
Keyword
效果
-lt
Less than (小於)
-le
Less then or equal (小於或等於)
-eq
Equal (等於)
-ge
Greater than or equal (大於或等於)
-gt
Greater than (大於)
-ne
Not equal (不等於)


for

for loop 在 bash 中當然也是不可欠缺的一員大將啦! 只是跟其他程式語言不同的地方,他並非以「數字」的方式直接指定 for loop 的程式要執行的次數,而是以一個串列的值(例如:1 3 4 6 8)去執行,有幾個值就執行幾次,因此格式大概如下:
for name [in list ]
do
statements that can use $name...
done
其實說穿了,使用方式就跟其他程式語言中的 for each 是差不多相同的,以下結合 if/else 判斷來個範例程式:
#!/bin/bash
IFS=":"
for dir in $PATH
do
if [ -z "$dir" ]; then
dir="."
fi

if ! [ -e "$dir" ]; then
echo "${dir} doesn't exist"
elif ! [ -d "${dir}" ]; then
echo "${dir} diesn't a directory"
else
ls -ld $dir
fi
done

另外再舉個例子,將 function、for、if/else 通通結合進來,下面這個程式的目的是以 recursive 的方式將參數中目錄底下的所有目錄列出,以下是程式內容:
echoDir() {
chrTab="${chrTab}t"

cd $1
for eachFilePath in $(ls)
do
if [ -d $eachFilePath ]; then
echo -e ${chrTab}${eachFilePath}
echoDir $eachFilePath
fi
done
cd ..

chrTab=${chrTab%'t'}
}

filePath=$1
chrTab=""

if [ -d $1 ]; then
cd $1
for eachFilePath in $(ls)
do
if [ -d $eachFilePath ]; then
echo $eachFilePath
echoDir $eachFilePath
fi
done
cd ..
fi
有興趣知道輸出結果的,可以 copy 回去自己試試看。


case

case 在 bash 中的用法其實就是跟 C、Java 等程式語言中的 switch case 差不多,以下是使用語法:
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac
以下用個簡單程式來說明:
for filename in "$@"; do
case $filename in
*.jpg | *.png ) echo "may be jpg or png" ;;
*.tga ) echo "tga" ;;
*.xpm ) echo "xpm" ;;
* ) echo "can not identified file type."
esac
done


select

這個部分就跟一般的程式語言很不一樣了! 而且僅在 bash 1.14 版以後才有支援喔! 透過 select,可以很容易的產生出選單,以下是使用 select 的語法:
select name [in list ]
do
statements that can use $name...
done
透過 select 語法,可以將 list 中的內容變成一個個的數字選項供使用者選擇,並且有兩個變數需要注意到,分別是 $name 以及 $REPLY;其中 $name 變數的值為選項的內容,而 $REPLY 的值則是選項所代表的數字。

當使用者輸入某選項的數字時,$name 與 $REPLY 就會存入相對應的值,但若是輸入一個非選項內的數字,則兩個變數的值都會變成 null,因此可以透過檢查 $name 變數值是否為 null 來判斷使用者有無輸入合法的選項。

以下用一個簡單的小程式來作範例:
#!/bin/bash

# 指定分隔 $PATH 的字元
IFS=":"
# 變數 PS3 儲存使用 select 時提示使用者的字串
PS3="which path you want to use? "
select mySelect in $PATH;
do
if [ $mySelect ]; then
echo "mySelect = ${mySelect}"
echo "REPLY = ${REPLY}"
else
echo "no such option!"
# 透過關鍵字「break」就可離開了!
break;
fi
done


# 以下是執行結果:
1) /usr/kerberos/sbin 4) /usr/local/bin 7) /usr/sbin
2) /usr/kerberos/bin 5) /sbin 8) /usr/bin
3) /usr/local/sbin 6) /bin 9) /root/bin
which path you want to use? 1
mySelect = /usr/kerberos/sbin
REPLY = 1
which path you want to use? 5
mySelect = /sbin
REPLY = 5
which path you want to use? 10
no such option!
至於 select 要如何應用在許許多多的管理工作上,就需要管理者發揮創意了!


while & until

在 while 與 until 的部分,也是跟一般程式語言中的用法差不多,以下分別介紹 while 與 until 的語法:

while
while condition do
statements... done
until
until command ; do statements... done
其實說穿了,while 與 until 不過就是一體兩面的用法而已,差別僅在於 while 判斷條件是否為 true,until 判斷條件是否為 false。

以下用兩段程式來說明,以下兩段程式的作用是相同的,只是分別以 while 與 until 來撰寫:
until [ cp $1 $2 ];
do
echo 'Attempt to cpoy failed. waiting...'
sleep 5
done
while [ ! cp $1 $2 ];
do
echo 'Attempt to cpoy failed. waiting...'
sleep 5
done
這裡僅是簡單用法的介紹,其他就靠大家發揮了!

2007年6月10日 星期日

基本 bash 程式設計 (4) - Command Substitution

到此為止,目前看到了兩種指定變數值的方式:
  1. script 中指定
  2. 以 position parameter 的方式指定

在這個部分中,要介紹更強大好用的變數指定方式,稱為 Command Substitution !

Command Substitution 的神奇之處在於「可以將各種命令的 standard output 作為變數的值」! 使用語法如下:
$(指令內容)
以下用一些範例來說明:
# 列出目前所在的目錄
echo $(pwd)

# 列出使用者家目錄的檔案
echo $(ls $HOME)

# 列出目前所在目錄下的檔案
echo $(ls $(pwd))

# 檔案中的斷行字元會被移除掉後印出其內容
echo $(< sort_data.txt)

接著再來個稍微複雜一些的,假設從主機中發信給目前登入主機的所有使用者,可以這樣做:
# 顯示目前所有登入 server 的使用者
who

# 取得所有使用者的清單
who | cut -d' ' -f1

# 寄信給他們囉!
mail $(who | cut -d' ' -f1)
最後,其他更加靈活的使用,就是在於管理者的創意與巧思了!

2007年6月3日 星期日

基本 bash 程式設計 (3) - String Operators

由於 shell script 重點相當著重在字串的處理,當然在 Linux 中有提供許多指令可以來進行字串的處理;然後,透過一些小技巧,就可以免去使用 pipe 搭配其他指令的麻煩,以下就慢慢來介紹...

String Substitution

在宣告變數時,有可能會因為疏忽而打錯;或是在寫給使用者使用的 script,每次都要詳細指定參數的麻煩....

其實這些錯誤或麻煩都可以避免,只要稍微修改一下變數的使用方式(搭配符號「{ }」)即可,以下用一張圖表來說明:

Operator
作用
目的
範例
${varname:-word}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則回傳 word
用於設定變數預設值
${count:-0}
若變數 count 未定義,則回傳 0
${varname:=word}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則設定此變數的值為 word,再回傳變數的值 用於將未定義的變數設定預設值
${count:=0}
若變數 count 未定義,則設定 count 變數的值為 0 並回傳
${varname:?message}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則顯示「varname: message」,並中斷目前的命令或 script。 用來針對未定義的變數進行 debug
${count:?"undefined!"}
若變數 count 未定義,則會顯示「count: undefined!」,並中斷程式執行
${varname:+word}
假如 varname 變數存在,並且不是 null,則回傳 word 用來測試變數是否存在
${count:+1}
若變數 count 存在,則回傳 1
${varname:offset:length}
回傳 varname 變數的值中,從第 offset 的字元開始(變數值的第一個位置為 0),長度為 length 的字串。
有以下幾種用法:
  1. 若 length 省略,則從 offset 位置取到最後
  2. 若 offset 小於 0,表示從後面的位置往回取
  3. 若 varname 為 @,則回傳第 offset 個參數開始算的第 length 個參數

取得變數所儲存字串的部分字串
count="frogfootman"
${count:4} => footman
${count:4:4} => foot

像是表格中的第一種用法,就相當適用於必須為變數指定預設值的情形下。

以下直接用個例子來說明使用方式,假設我們檔案「sort-data.txt」,內容如下:
8 eight
9 nine
1 one
2 two
3 three
7 seven
5 five
6 six
4 four
0 zero
接著要寫一支 script 來根據最前面的數字作排序,並顯示指定的行數:
#!/bin/bash
# 檔案名稱:highest.sh

# 檢查 file 名稱是否有輸入
fileName=${1:?"filename missing, please check your file."}

# 設定預設顯示的行數
# 若使用者未輸入第二個參數就預設為 5
displayLine=${2:-5}

# sort 是用來排序的程式
# 參數「n」指定以數字為依據作排序
# 參數「r」則是 reverse 的意思,由大排到小
# head 是用來取「前幾行」
sort -nr $fileName | head -$displayLine
執行結果如下:
# 沒指定檔名時
shell> sh highest.sh
highest.sh: line 5: 1: filename missing, please check your file.

# 未指定所要顯示的行數
shell> sh highest.sh sort-data.txt
9 nine
8 eight
7 seven
6 six
5 five

# 指定顯示行數
shell> sh highest.sh sort-data.txt 3
9 nine
8 eight
7 seven


Patterns & Pattern Matching

說到字串處理,怎麼可以缺乏正規表示式呢?

當然 bash 中提供的不是正規表示式,不過提供的也是類似的功能,以下用一個表格來介紹:

Operator
作用
${variable#pattern}
從字串開頭開始比對,當 pattern 符合變數中的值,即不需繼續比對,僅比對至最短的符合字串,刪除比對到的部分並回傳剩下的變數值
${variable##pattern} 同上,會回傳刪除比對後的變數值,但所採取的是貪婪比對,會一直比對到最長的部分為止
${variable%pattern} 從字串尾端開始比對,當 pattern 符合變數中的值,即不需繼續比對,僅比對至最短的符合字串,刪除比對到的部分並回傳剩下的變數值
${variable%%pattern}
同上,會回傳刪除比對後的變數值,但所採取的是貪婪比對,會一直比對到最長的部分為止
${variable/pattern/string}
差異處:
在 variable 與 pattern 比對後,在上面的 operator 中,只有第一個比對到的字串會被更換為 string;在下面的 operator 中則會取代所有比對到的字串。
相同處:
  1. 若 pattern 開始加上「#」,就會比對開頭
  2. 若 pattern 開始加上「%」,則會比對結尾
  3. 若 string 沒有指定,則比對符合的部分會刪除(其實就是取代為空字串啦!)
  4. 若 variable 為「@」或「*」,則會逐一比對所有 position parameter
${variable//pattern/string}

以下直接用範例來說明:
myPath="/home/cam/book/long.file.name"
# 將兩個「/」之間的內容都刪除(貪婪比對)
echo ${myPath##/*/} # long.file.name
# 效果同上(因為 * 也可以代表 /)
echo ${myPath##*/} # long.file.name
# 將兩個「/」之間的內容刪除
echo ${myPath#/*/} # cam/book/long.file.name
# 從尾端開始比對,將比對符合結果刪除
echo ${myPath%.*} # /home/cam/book/long.file
# 從尾端開始比對,將比對符合結果刪除(貪婪比對)
echo ${myPath%%.*} # /home/cam/book/long

myPhotoName="myphoto.png"
# 將副檔名 png 改為 jpg
echo ${myPhotoName%.png}.jpg # myphoto.jpg

myEnvPath="/home/user/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin"
# 將「:」全部換成斷行字元,結果如下:
# /home/user/bin
# /usr/local/bin
# /bin
# /usr/bin
# /usr/X11R6/bin
echo -e ${myEnvPath//:/'\n'}
繼續提到上面說的 pattern,有更進階的用法,可以進行多重比對,只要把每個 pattern 以符號「|」相隔即可,以下用表格來說明:
Operator
說明
範例
效果
*(PatternList)
符合 0 個到多個指定的 pattern 清單
*(alice|hatter|hare)
字串中出現 alice 或是 hatter 或是 hare,甚至完全沒有,都符合
+(PatternList) 符合 1 個到多個指定的 pattern 清單 +(alice|hatter|hare) 字串中出現 alice 或是 hatter 或是 hare 都符合
+[0-9]
僅數字符合
?(PatternList) 符合 0 個到 1 個指定的 pattern 清單 ?(alice|hatter|hare) 字串中僅出現一次 alice 或是 hatter 或是 hare,或是完全沒有,就符合
@(PatternList) 剛好完全符合指定 pattern 清單的其中 1 個
@(alice|hatter|hare) 僅有 alice、hatter、hare 這三個字串符合(精確比對)
!(PatternList) 完全不符合指定的 pattern 清單
!(alice|hatter|hare) 只要字串中沒有 alice、hatter、hare 即符合
!(vt+([0-9]))
只要字串中沒有「vt+數字」的部分即符合

至於如何使用,就要靠使用者的創意與巧思了!


Length Operator

最後,要來介紹如何取得變數值的長度,這很簡單,語法如下:
${#varname}
以下來個簡單的範例:
myFile="myfile.txt"
echo ${#myFile} # 輸出 10

2007年6月2日 星期六

基本 bash 程式設計 (2) - Variable

前言

Variable(變數),是每個程式語言中都不可或缺的重要角色之一,當然 shell script 也不例外!

不像其他程式語言,variable 擁有許多不同的型態,甚至可以是 object;在 shell script 中,variable 中儲存的內容幾乎是 character(字元)、string(字串)、number(數字)。

而關於 variable 的使用方式之前就有提過,因此這邊不再多提。


Positional Parameters

不知道是否有人曾經想過,如果執行 shell script 時想要加上多個參數,應該要怎麼下指令呢? 其實很簡單,跟執行一般程式沒兩樣,大致就是以下格式:
shell> ./script_name para1 para2 para3 ....... paraN
那在 script 中要如何使用這些參數呢? 答案就是「Positional Parameter」!

而 Positional Parameter 是以 0 1 2 3 4 .... 等數字來命名的,其中 0 是 script 名稱,而之後就是所接的參數,因此就上面的例子來說:
  • $0=script_name
  • $1=para1
  • $2=para2
  • $3=para3
  • .......(以此類推)
  • $N=paraN
由於這些參數的使用是類似其他程式語言中 index 的使用方式,才會被稱作是 Positional Parameter。

此外,當然還有一些額外且常用的 Positional Parameter,以下直接列出他們的效果:
  • $@="$1 $2 $3 ...... $N"
    為所有參數組合而成的字串,以 space 隔開,若 script 沒有參數就是空字串囉!
  • $#=N
    參數數量,若 script 沒有參數就是 0 了
  • $*="$0$IFS$1$IFS$2$IFS$3$IFS ....... $N"
    包含所有 Positional Parameter(包括 script name) 內容,且用 $IFS 所隔開的字串;一般來說 $IFS 有可能是 space、TAB、NEWLINE 三種其中一個!
    簡單來說,「$*」與「$@」的差別就是是否有包含 $0 的內容而已。
    【註】書上是這樣說,但在 Ubuntu 中實測並非如此,而是跟 $@ 是相同的

以下用一個簡單的例子來說明:

alice.sh 的內容:
#!/bin/bash
echo "alice: $@"
echo "$0 $1 $2 $3 $4"
echo "$# arguments"
echo "$*"
執行 alice.sh
shell> sh alice.sh in wonderland

# 以下為輸出結果:
alice: in wonderland
alice.sh in wonderland
2 arguments
in wonderland

另外,除了在執行程式時使用 Position Parameter 的功能外,也可以在 function 中使用喔! 以下舉個簡單例子:
#!/bin/bash

alice() {
echo "alice: $@"
echo "$0 $1 $2 $3 $4"
echo "$# arguments"
echo "$*"
}

alice in wonderland
此時直接執行程是不需帶參數:(結果跟上面是相同的喔!)
shell> sh alice.sh


Variable 有效範圍

在 variable 的有效範圍這個部分,shell script 與一般的程式語言不太一樣,在 function 中所定義的 variable 是 global 的! 而非 local 的喔!

這邊直接以範例來解釋! 請注意 $var1 囉!
#!/bin/bash

myFunc() {
echo "in function: $0 $1 $2"
var1="in function"
echo "var1: $var1"
}

var1="outside function"
echo "var1: $var1"
echo "$0: $1 $2"

myFunc funcArg1 funcArg2

echo "var1: $var1"
echo "$0: $1 $2"
接著執行程式囉!
shell> sh var_scope.sh arg1 arg2

# 以下為執行結果:
var1: outside function
var_scope.sh: arg1 arg2
in function: var_scope.sh funcArg1 funcArg2
var1: in function
var1: in function # 注意此行! $var1 的值被 function 改變囉!
var_scope.sh: arg1 arg2 # position parameter 不會被 function 影響到!


可能此時有人會問,script 要如何撰寫,才能讓 function 中的變數不會影響到 function 外面的變數呢? 其實很簡單,只要透過關鍵字「local」即可,如果將上面程式中的 function 改為以下內容:
myFunc() {
local var1 # 將變數加上了 local 關鍵字
echo "in function: $0 $1 $2"
var1="in function"
echo "var1: $var1"
}
出來的結果就會跟一般的程式語言相同的,結果如下:
shell> sh var_scope.sh arg1 arg2

# 以下為執行結果:
var1: outside function
var_scope.sh: arg1 arg2
in function: var_scope.sh funcArg1 funcArg2
var1: in function
var1: outside function # 果然 script 中的變數沒被 function 所影響到
var_scope.sh: arg1 arg2
以下用兩張圖來說明使用「local」關鍵字後,變數範圍的差異:
未用 local 變數
使用 local 變數後


最後,大家都知道要使用變數,必須在變數名稱前加上符號「$」,但假設有一個變數的名稱為 TEST,然後要在螢幕上輸出變數內容,在加一條底線,但如果使用以下方式:
echo $TEST_
這時系統會去判斷是否有名稱為「TEST_」的變數,結果當然是沒有,所以會輸出空白! 而為了得到正確的結果,可以在變數名稱旁邊加上左右大括號「{}」,所以只要改成以下語法就正常了:
echo ${TEST}_

2007年5月26日 星期六

基本 bash 程式設計 (1) - Script & Function

Shell Script 簡介

其實 shell script 說穿了,就是一群指令的集合而已,不過藉由程式設計師的創意,可以寫出功能強大的 shell script 來完成許多複雜的工作,對一個想要讓管理工作更有效率(其實是想偷懶)的管理者來說,實在是不能不學的一門課程。

一些廢話就不說了,應該大家都知道 Linux 的檔案權限吧,shell script 沒有執行(x)權限是沒辦法使用的喔!

再來說明 Shell Script 執行的方式,假設我們有一支 shell script 稱為 alice,裡面有兩行指令,分別是 hatter 以及 gryphon,在 Linux 中,一共有三種方法可以來執行這支 shell script,分別是:
(a) source alice
(b) ./alice
(c) source alice &
這三種方式,都可以順利的執行 alice 這一支 script,但是.......有何差別呢? 答案是....「script 是否進入 subshell 中執行」。

以下用一張圖來說明三種執行方式的差異:

看出來了嗎? 在 (a) 與 (b) 中,使用者都必須等待程式執行完畢,才能接手繼續工作,而兩者的差異在於 script 是否進入了 subshell 中執行;或許一般人覺得這兩種方式似乎沒有差異,但試想,如果管理者在 subshell 修改了許多 shell 的環境設定,此時 script 進入 subshell 中執行就變的很不一樣了! (例如:有不同的 PATH 設定、其他各種 alias command....等等 )

而第三種呢? 除了 script 進入 subshell 中執行外,而且還是在背景處理,完全不會干擾到管理者目前的工作,此種執行方式在於 script 需要跑相當長一段時間的情況下就相當好用了,管理者可以斟酌使用。


Function (函式) 的使用

現在任何高階的程式語言都講求物件導向了,shell script 當然不能連最基本的 function 功能都不支援囉! 而使用 function 的好處有兩個:
  1. 功能模組化
    這不需要費心解釋吧? 這不就是 function 的主要目的嗎?
  2. 加快速度
    shell script 的 function 就像 Java 中 class 的 static method 一樣,系統會預先配置記憶體位置給他,因此可以直接使用且速度較快! 一般來說,目前電腦的記憶體都相當的大,因此在一般情況下(embedded system 中例外啦....)不需要為了省一點記憶體空間都不用 function,利用 function 可以大大降低工作的複雜度。

而 function 定義的方式有兩種,分別是:
function 函式名稱 {
......

......

}
或是
函式名稱() {
......

......

}
====== 以下為備註 ======
第一種方式在「Ubuntu」中直接執行 shell script 會產生錯誤喔! 原因是系統會將「function 函式名稱」視為指令來執行....所以就錯了.....(在 RedHat 系列的 Linux 就很正常!)

不過有個很簡單的方法可以處理,只要在執行之前先 source 該 script 即可。

而上述兩種撰寫方式所執行出來的並無不同,管理者儘管選擇自己喜歡的去寫即可。

而在每個使用者登入 Linux 系統時,其實就已經存在很多已經預先定義好的 function,可以打下列指令列出預先定義的 function 及其內容:
shell> declare -f
而 function 跟 script 有何不同呢? 以下分兩點說明:
  1. function 不會執行於 subshell,因此與 script 是在同一個 process 中完成
  2. 當 script 名稱與 function 名稱相同時,執行時會以執行 function 為優先

另外附帶,關於執行順序,優先順序如下:
  1. Alias
  2. 系統中的關鍵字
  3. 函式
  4. 系統中內建的工具
  5. script 或其他執行程式

若有遇到同名的問題,管理者必須瞭解執行的優先順序為何,才有辦法順利的 debug。

基本上,不論是 alias、keyword、function、或是其他程式,都有一個名稱(name),而同樣的名稱,又有可能是 alias、keyword、function.....的名稱,那應該要如何判斷某個名稱(name)的型態為何呢? Linux 中提供了一個好用的工具指令「type」,以下擷取 type 的 manpage 內容:
type [-aftpP] name [name ...]
With no options, indicate how each name would be interpreted if
used as a command name. If the -t option is used, type prints a
string which is one of alias, keyword, function, builtin, or
file if name is an alias, shell reserved word, function,
builtin, or disk file, respectively. If the name is not found,
then nothing is printed, and an exit status of false is
以下舉幾個使用 type 的例子:
# 顯示「file」
shell> type -t bash

# 顯示「keyword」
shell> type -t if
或許平常 type 使用到的機會可能不多,不過或許有天會成為 debug 的利器也說不定喔!


參考資料:
  1. Introduction To Bash Shell Scripting
  2. Learning The Bash Shell, 3/e