建立一個腳本
Linux中有好多中不同的shell
但是通常我們使用bash (bourne again shell) 進行shell編程
因為bash是免費的並且很容易使用
所以在本文中筆者所提供的腳本都是使用bash(但是在大多數情況下
這些腳本同樣可以在 bash的大姐
bourne shell中運行)
如同其他語言一樣
通過我們使用任意一種文字編輯器
比如nedit
kedit
emacs
vi
等來編寫我們的shell程序
程序必須以下面的行開始(必須方在文件的第一行)
#!/bin/sh
符號#!用來告訴系統它後面的參數是用來執行該文件的程序
在這個例子中我們使用/bin/sh來執行程序
當編輯好腳本時
如果要執行該腳本
還必須使其可執行
要使腳本可執行
chmod +x filename
然後
您可以通過輸入
/filename 來執行您的腳本
注釋
在進行shell編程時
以#開頭的句子表示注釋
直到這一行的結束
我們真誠地建議您在程序中使用注釋
如果您使用了注釋
那麼即使相當長的時間內沒有使用該腳本
您也能在很短的時間內明白該腳本的作用及工作原理
變量
在其他編程語言中您必須使用變量
在shell編程中
所有的變量都由字符串組成
並且您不需要對變量進行聲明
要賦值給一個變量
您可以這樣寫
變量名=值
取出變量值可以加一個美元符號($)在變量前面
#!/bin/sh
#對變量賦值
a=
hello world
# 現在打印變量a的內容
echo
A is:
echo $a
在您的編輯器中輸入以上內容
然後將其保存為一個文件first
之後執行chmod +x first
使其可執行
最後輸入
/first執行該腳本
這個腳本將會輸出
A is:
hello world
有時候變量名很容易與其他文字混淆
比如
num=
echo
this is the $numnd
這並不會打印出
this is the
nd
而僅僅打印
this is the
因為shell會去搜索變量numnd的值
但是這個變量時沒有值的
可以使用花括號來告訴shell我們要打印的是num變量
num=
echo
this is the ${num}nd
這將打印
this is the
nd
有許多變量是系統自動設定的
這將在後面使用這些變量時進行討論
如果您需要處理數學表達式
那麼您需要使用諸如expr等程序(見下面)
除了一般的僅在程序內有效的shell變量以外
還有環境變量
由export關鍵字處理過的變量叫做環境變量
我們不對環境變量進行討論
因為通常情況下僅僅在登錄腳本中使用環境變量
Shell命令和流程控制
在shell腳本中可以使用三類命令
)Unix 命令:
雖然在shell腳本中可以使用任意的unix命令
但是還是由一些相對更常用的命令
這些命令通常是用來進行文件和文字操作的
常用命令語法及功能
echo
some text
: 將文字內容打印在屏幕上
ls: 文件列表
wc –l filewc
w filewc
c file: 計算文件行數計算文件中的單詞數計算文件中的字符數
cp sourcefile destfile: 文件拷貝
mv oldname newname : 重命名文件或移動文件
rm file: 刪除文件
grep
pattern
file: 在文件內搜索字符串比如
grep
searchstring
file
txt
cut
b colnum file: 指定欲顯示的文件內容范圍
並將它們輸出到標准輸出設備比如
輸出每行第
個到第
個字符cut
b
file
txt千萬不要和cat命令混淆
這是兩個完全不同的命令
cat file
txt: 輸出文件內容到標准輸出設備(屏幕)上
file somefile: 得到文件類型
read var: 提示用戶輸入
並將輸入賦值給變量
sort file
txt: 對file
txt文件中的行進行排序
uniq: 刪除文本文件中出現的行列比如
sort file
txt | uniq
expr: 進行數學運算Example: add
and
expr
+
find: 搜索文件比如
根據文件名搜索find
name filename
print
tee: 將數據輸出到標准輸出設備(屏幕) 和文件比如
somecommand | tee outfile
basename file: 返回不包含路徑的文件名比如
basename /bin/tux將返回 tux
dirname file: 返回文件所在路徑比如
dirname /bin/tux將返回 /bin
head file: 打印文本文件開頭幾行
tail file : 打印文本文件末尾幾行
sed: Sed是一個基本的查找替換程序
可以從標准輸入(比如命令管道)讀入文本
並將結果輸出到標准輸出(屏幕)
該命令采用正則表達式(見參考)進行搜索
不要和shell中的通配符相混淆
比如
將linuxfocus 替換為 LinuxFocus
cat text
file | sed
s/linuxfocus/LinuxFocus/
> newtext
file
awk: awk 用來從文本文件中提取字段
缺省地
字段分割符是空格
可以使用
F指定其他分割符
cat file
txt | awk
F
{print $
$
}
這裡我們使用
作為字段分割符
同時打印第一個和第三個字段
如果該文件內容如下
Adam Bor
IndiaKerry Miller
USA命令輸出結果為
Adam Bor
IndiaKerry Miller
USA
) 概念: 管道
重定向和 backtick
這些不是系統命令
但是他們真的很重要
管道 (|) 將一個命令的輸出作為另外一個命令的輸入
grep
hello
file
txt | wc
l
在file
txt中搜索包含有
hello
的行並計算其行數
在這裡grep命令的輸出作為wc命令的輸入
當然您可以使用多個命令
重定向
將命令的結果輸出到文件
而不是標准輸出(屏幕)
> 寫入文件並覆蓋舊文件
>> 加到文件的尾部
保留舊文件內容
反短斜線
使用反短斜線可以將一個命令的輸出作為另外一個命令的一個命令行參數
命令
find
mtime
type f
print
用來查找過去
小時(
mtime –
則表示過去
小時)內修改過的文件
如果您想將所有查找到的文件打一個包
則可以使用以下腳本
#!/bin/sh
# The ticks are backticks (`) not normal quotes (
):
tar
zcvf lastmod
tar
gz `find
mtime
type f
print`
) 流程控制
if
表達式 如果條件為真則執行then後面的部分
if
; then
elif
; then
else
fi
大多數情況下
可以使用測試命令來對條件進行測試
比如可以比較字符串
判斷文件是否存在及是否可讀等等…
通常用
[ ]
來表示條件測試
注意這裡的空格很重要
要確保方括號的空格
[
f
somefile
]
判斷是否是一個文件
[
x
/bin/ls
]
判斷/bin/ls是否存在並有可執行權限
[
n
$var
]
判斷$var變量是否有值
[
$a
=
$b
]
判斷$a和$b是否相等
執行man test可以查看所有測試表達式可以比較和判斷的類型
直接執行以下腳本
#!/bin/sh
if [
$SHELL
=
/bin/bash
]; then
echo
your login shell is the bash (bourne again shell)
else
echo
your login shell is not bash but $SHELL
fi
變量$SHELL包含了登錄shell的名稱
我們和/bin/bash進行了比較
快捷操作符
熟悉C語言的朋友可能會很喜歡下面的表達式
[
f
/etc/shadow
] && echo
This computer uses shadow passwors
這裡 && 就是一個快捷操作符
如果左邊的表達式為真則執行右邊的語句
您也可以認為是邏輯運算中的與操作
上例中表示如果/etc/shadow文件存在則打印
This computer uses shadow passwors
同樣或操作(||)在shell編程中也是可用的
這裡有個例子
#!/bin/sh
mailfolder=/var/spool/mail/james
[
r
$mailfolder
]
{ echo
Can not read $mailfolder
; exit
; }
echo
$mailfolder has mail from:
grep
^From
$mailfolder
該腳本首先判斷mailfolder是否可讀
如果可讀則打印該文件中的
From
一行
如果不可讀則或操作生效
打印錯誤信息後腳本退出
這裡有個問題
那就是我們必須有兩個命令
打印錯誤信息
退出程序
我們使用花括號以匿名函數的形式將兩個命令放到一起作為一個命令使用
一般函數將在下文提及
不用與和或操作符
我們也可以用if表達式作任何事情
但是使用與或操作符會更便利很多
case表達式可以用來匹配一個給定的字符串
而不是數字
case
in
) do something here ;;
esac
讓我們看一個例子
file命令可以辨別出一個給定文件的文件類型
比如
file lf
gz
這將返回
lf
gz: gzip compressed data
deflated
original filename
last modified: Mon Aug
:
:
os: Unix
我們利用這一點寫了一個叫做smartzip的腳本
該腳本可以自動解壓bzip
gzip 和zip 類型的壓縮文件
#!/bin/sh
ftype=`file
$
`
case
$ftype
in
$
: Zip archive
*)
unzip
$
;;
$
: gzip compressed
*)
gunzip
$
;;
$
: bzip
compressed
*)
bunzip
$
;;
*) error
File $
can not be uncompressed with smartzip
;;
esac
您可能注意到我們在這裡使用了一個特殊的變量$
該變量包含了傳遞給該程序的第一個參數值
也就是說
當我們運行
smartzip articles
zip
$
就是字符串 articles
zip
select 表達式是一種bash的擴展應用
尤其擅長於交互式使用
用戶可以從一組不同的值中進行選擇
select var in
; do
break
done
now $var can be used
下面是一個例子
#!/bin/sh
echo
What is your favourite OS?
select var in
Linux
Gnu Hurd
Free BSD
Other
; do
break
done
echo
You have selected $var
下面是該腳本運行的結果
What is your favourite OS?
) Linux
) Gnu Hurd
) Free BSD
) Other
#?
You have selected Linux
您也可以在shell中使用如下的loop表達式
while
; do
done
while
loop 將運行直到表達式測試為真
will run while the expression that we test for is true
關鍵字
break
用來跳出循環
而關鍵字
continue
用來不執行余下的部分而直接跳到下一個循環
for
loop表達式查看一個字符串列表 (字符串用空格分隔) 然後將其賦給一個變量
for var in
; do
done
在下面的例子中
將分別打印ABC到屏幕上
復制代碼 代碼如下:
#!/bin/sh
for var in A B C ; do
echo var is $var
done
下面是一個更為有用的腳本showrpm
其功能是打印一些RPM包的統計信息
復制代碼 代碼如下:
#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile rpmfile
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*rpm
for rpmpackage in $*; do
if [ r $rpmpackage ];then
echo =============== $rpmpackage ==============
rpm qi p $rpmpackage
else
echo ERROR: cannot read file $rpmpackage
fi
done
這裡出現了第二個特殊的變量$*
該變量包含了所有輸入的命令行參數值
如果您運行showrpm openssh
rpm w
m
rpm webgrep
rpm
此時 $* 包含了
個字符串
即openssh
rpm
w
m
rpm and webgrep
rpm
引號
在向程序傳遞任何參數之前
程序會擴展通配符和變量
這裡所謂擴展的意思是程序會把通配符(比如*)替換成合適的文件名
它變量替換成變量值
為了防 止程序作這種替換
您可以使用引號
讓我們來看一個例子
假設在當前目錄下有一些文件
兩個jpg文件
mail
jpg 和tux
jpg
#!/bin/sh
echo *
jpg
這將打印出
mail
jpg tux
jpg
的結果
引號 (單引號和雙引號) 將防止這種通配符擴展
#!/bin/sh
echo
*
jpg
echo
*
jpg
這將打印
*
jpg
兩次
單引號更嚴格一些
它可以防止任何變量擴展
雙引號可以防止通配符擴展但允許變量擴展
#!/bin/sh
echo $SHELL
echo
$SHELL
echo
$SHELL
運行結果為
/bin/bash
/bin/bash
$SHELL
最後
還有一種防止這種擴展的方法
那就是使用轉義字符——反斜桿
echo *
jpg
echo $SHELL
這將輸出
*
jpg
$SHELL
Here documents
當要將幾行文字傳遞給一個命令時
here documents(譯者注
目前還沒有見到過對該詞適合的翻譯)一種不錯的方法
對每個腳本寫一段幫助性的文字是很有用的
此時如果我們四有那個 here documents就不必用echo函數一行行輸出
一個
Here document
以 << 開頭
後面接上一個字符串
這個字符串還必須出現在here document的末尾
下面是一個例子
在該例子中
我們對多個文件進行重命名
並且使用here documents打印幫助
復制代碼 代碼如下:
#!/bin/sh
# we have less than arguments Print the help text:
if [ $# lt ] ; then
cat <
ren renames a number of files using sed regular expressions
USAGE: ren regexp replacement files
EXAMPLE: rename all *HTM files in l:
ren HTM$ html *HTM
HELP
exit
fi
OLD=$
NEW=$
# The shift command removes one argument from the list of
# command line arguments
shift
shift
# $* contains now all the files:
for file in $*; do
if [ f $file ] ; then
newfile=`echo $file | sed s/${OLD}/${NEW}/g`
if [ f $newfile ]; then
echo ERROR: $newfile exists already
else
echo renaming $file to $newfile
mv $file $newfile
fi
fi
done
這是一個復雜一些的例子
讓我們詳細討論一下
第一個if表達式判斷輸入命令行參數是否小於
個 (特殊變量$# 表示包含參數的個數)
如果輸入參數小於
個
則將幫助文字傳遞給cat命令
然後由cat命令將其打印在屏幕上
打印幫助文字後程序退出
如果輸入參數等於或大於
個
我們就將第一個參數賦值給變量OLD
第二個參數賦值給變量NEW
下一步
我們使用shift命令將第一個和第二個參數從參數列表中刪除
這樣原來的第三個參數就成為參數列表$*的第一個參數
然後我們開始循環
命令行參數列表被一個接一個地被賦值給變量$file
接著我們判斷該文件是否存在
如果存在則通過sed命令搜索和替換來產生新的文件名
然後將反短斜線內命令結果賦值給newfile
這樣我們就達到了我們的目的
得到了舊文件名和新文件名
然後使用mv命令進行重命名
函數
如果您寫了一些稍微復雜一些的程序
您就會發現在程序中可能在幾個地方使用了相同的代碼
並且您也會發現
如果我們使用了函數
會方便很多
一個函數是這個樣子的
復制代碼 代碼如下:
functionname()
{
# inside the body $ is the first argument given to the function
# $ the second
body
}
您需要在每個程序的開始對函數進行聲明
下面是一個叫做xtitlebar的腳本
使用這個腳本您可以改變終端窗口的名稱
這裡使用了一個叫做help的函數
正如您可以看到的那樣
這個定義的函數被使用了兩次
復制代碼 代碼如下:
#!/bin/sh
# vim: set sw= ts= et:
help()
{
cat <
xtitlebar change the name of an xterm gnometerminal or kde konsole
USAGE: xtitlebar [h] string_for_titelbar
OPTIONS: h help text
EXAMPLE: xtitlebar cvs
HELP
exit
}
# in case of error or if h is given we call the function help:
[ z $ ] && help
[ $ = h ] && help
# send the escape sequence to change the xterm titelbar:
echo e ];$
#
在腳本中提供幫助是一種很好的編程習慣
這樣方便其他用戶(和您)使用和理解腳本
命令行參數
我們已經見過$* 和 $
$
$
等特殊變量
這些特殊變量包含了用戶從命令行輸入的參數
迄今為止
我們僅僅了解了一些簡單的命令行語法(比如一些強制性的參數和查看幫助的
h選項)
但是在編寫更復雜的程序時
您可能會發現您需要更多的自定義的選項
通常的慣例是在所有可選的參數之前加一個減號
後面再加上參數值 (比如文件名)
有好多方法可以實現對輸入參數的分析
但是下面的使用case表達式的例子無遺是一個不錯的方法
復制代碼 代碼如下:
#!/bin/sh
help()
{
cat <
This is a generic command line parser demo
USAGE EXAMPLE: cmdparser l hello f somefile somefile
HELP
exit
}
while [ n $ ]; do
case $ in
h) help;shift ;; # function help is called
f) opt_f=;shift ;; # variable opt_f is set
l) opt_l=$;shift ;; # l takes an argument > shift by
) shift;break;; # end of options
*) echo error: no such option $ h for help;exit ;;
*) break;;
esac
done
echo opt_f is $opt_f
echo opt_l is $opt_l
echo first arg is $
echo nd arg is $
您可以這樣運行該腳本
cmdparser
l hello
f
somefile
somefile
返回的結果是
opt_f is
opt_l is hello
first arg is
somefile
nd arg is somefile
這個腳本是如何工作的呢?腳本首先在所有輸入命令行參數中進行循環
將輸入參數與case表達式進行比較
如果匹配則設置一個變量並且移除該參數
根據unix系統的慣例
首先輸入的應該是包含減號的參數
實例
一般編程步驟
現在我們來討論編寫一個腳本的一般步驟
任何優秀的腳本都應該具有幫助和輸入參數
並且寫一個偽腳本(framework
sh)
該腳本包含了大多數腳本都需要的框架結構
是一個非常不錯的主意
這時候
在寫一個新的腳本時我們只需要執行一下copy命令
cp framework
sh myscript
然後再插入自己的函數
讓我們再看兩個例子
二進制到十進制的轉換
腳本 b
d 將二進制數 (比如
) 轉換為相應的十進制數
這也是一個用expr命令進行數學運算的例子
復制代碼 代碼如下:
#!/bin/sh
# vim: set sw= ts= et:
help()
{
cat <
bh convert binary to decimal
USAGE: bh [h] binarynum
OPTIONS: h help text
EXAMPLE: bh
will return
HELP
exit
}
error()
{
# print an error and exit
echo $
exit
}
lastchar()
{
# return the last character of a string in $rval
if [ z $ ]; then
# empty string
rval=
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo n $ | wc c | sed s/ //g `
# now cut out the last char
rval=`echo n $ | cut b $numofchar`
}
chop()
{
# remove the last character in string and return it in $rval
if [ z $ ]; then
# empty string
rval=
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo n $ | wc c | sed s/ //g `
if [ $numofchar = ]; then
# only one char in string
rval=
return
fi
numofcharminus=`expr $numofchar `
# now cut all but the last char:
rval=`echo n $ | cut b ${numofcharminus}`
}
while [ n $ ]; do
case $ in
h) help;shift ;; # function help is called
) shift;break;; # end of options
*) error error: no such option $ h for help;;
*) break;;
esac
done
# The main program
sum=
weight=
# one arg must be given:
[ z $ ] && help
binnum=$
binnumorig=$
while [ n $binnum ]; do
lastchar $binnum
if [ $rval = ]; then
sum=`expr $weight + $sum`
fi
# remove the last position in $binnum
chop $binnum
binnum=$rval
weight=`expr $weight * `
done
echo binary $binnumorig is decimal $sum
#
該腳本使用的算法是利用十進制和二進制數權值 (
)
比如二進制
可以這樣轉換成十進制
*
+
*
=
為了得到單個的二進制數我們是用了lastchar 函數
該函數使用wc –c計算字符個數
然後使用cut命令取出末尾一個字符
Chop函數的功能則是移除最後一個字符
文件循環程序
或許您是想將所有發出的郵件保存到一個文件中的人們中的一員
但是在過了幾個月以後
這個文件可能會變得很大以至於使對該文件的訪問速度變慢
下面的腳本rotatefile 可以解決這個問題
這個腳本可以重命名郵件保存文件(假設為outmail)為outmail
而對於outmail
就變成了outmail
等等等等
復制代碼 代碼如下:
#!/bin/sh
# vim: set sw= ts= et:
ver=
help()
{
cat <
rotatefile rotate the file name
USAGE: rotatefile [h] filename
OPTIONS: h help text
EXAMPLE: rotatefile out
This will eg rename out to out out to out out to out
and create an empty outfile
The max number is
version $ver
HELP
exit
}
error()
{
echo $
exit
}
while [ n $ ]; do
case $ in
h) help;shift ;;
) break;;
*) echo error: no such option $ h for help;exit ;;
*) break;;
esac
done
# input check:
if [ z $ ] ; then
error ERROR: you must specify a file use h for help
fi
filen=$
# rename any etc file:
for n in ; do
if [ f $filen$n ]; then
p=`expr $n + `
echo mv $filen$n $filen$p
mv $filen$n $filen$p
fi
done
# rename the original file:
if [ f $filen ]; then
echo mv $filen $filen
mv $filen $filen
fi
echo touch $filen
touch $filen
這個腳本是如何工作的呢?在檢測用戶提供了一個文件名以後
我們進行一個
到
的循環
文件
被命名為
文件
重命名為
等等
循環完成之後
我們將原始文件命名為文件
同時建立一個與原始文件同名的空文件
調試
最簡單的調試命令當然是使用echo命令
您可以使用echo在任何懷疑出錯的地方打印任何變量值
這也是絕大多數的shell程序員要花費
%的時間來調試程序的原因
Shell程序的好處在於不需要重新編譯
插入一個echo命令也不需要多少時間
shell也有一個真實的調試模式
如果在腳本
strangescript
中有錯誤
您可以這樣來進行調試
sh
x strangescript
這將執行該腳本並顯示所有變量的值
shell還有一個不需要執行腳本只是檢查語法的模式
可以這樣使用
sh
n your_script
這將返回所有語法錯誤
我們希望您現在可以開始寫您自己的shell腳本
希望您玩得開心
From:http://tw.wingwit.com/Article/program/yxkf/201404/30421.html