本文用大量的例子闡述了如何編寫shell腳本。
為什麽要shell編程?
在Linux系統中,雖然有各種圖形界面工具,但sell仍然是壹個非常靈活的工具。Shell不僅是命令的集合,也是壹種偉大的編程語言。使用shell可以自動化很多任務,shell特別擅長系統管理任務,尤其是那些易用性、可維護性、可移植性比效率更重要的任務。
讓我們來看看shell是如何工作的:
創建腳本
Linux中有許多不同的shell,但通常我們使用bash (bourne又是shell)進行shell編程,因為bash是免費的並且易於使用。因此,在本文中,作者提供的腳本都使用了bash(但大多數情況下,這些腳本也可以運行在bash的大姐——bourne shell中)。
就像其他語言壹樣,我們可以使用任何文本編輯器,比如nedit、kedit、emacs、vi。
等著寫我們的shell程序。
程序必須以下面壹行開始(它必須在文件的第壹行):
#!/bin/sh
符號#!用來告訴系統,後面的參數用來執行文件的程序。在這個例子中,我們使用/bin/sh來執行程序。
編輯腳本時,如果要執行它,必須使它可執行。
要使腳本可執行:
chmod +x文件名
然後,您可以通過輸入:。/文件名。
給…作註解
在shell編程中,以#開頭的句子表示註釋,直到本行結束。我們真誠地建議您在程序中使用註釋。如果使用註釋,即使很久沒有使用腳本,也能在短時間內理解腳本的功能和工作原理。
可變的
您必須在其他編程語言中使用變量。在shell編程中,所有的變量都是由字符串組成的,不需要聲明變量。要給變量賦值,可以寫:
變量名=值
取出變量值時,可以在變量前放壹個美元符號($):
#!/bin/sh
#為變量賦值:
a="hello world "
#現在打印變量a的內容:
回聲“A是:”
echo $a
在編輯器中輸入上述內容,並首先將其保存為文件。然後先執行chmod +x。
使之可執行,最後進入。/首先執行腳本。
該腳本將輸出:
壹個是:
妳好世界
有時變量名很容易與其他單詞混淆,例如:
數量=2
echo“這是$numnd”
這樣就不會打印出“這是2號”,而只會打印出“這是”,因為shell會搜索變量numnd的值,但是這個變量沒有值。您可以使用花括號告訴shell我們想要打印num變量:
數量=2
echo "這是第$ { num }個"
這將打印:這是第二次
系統自動設置的變量有很多,後面使用這些變量的時候會討論。
如果需要處理數學表達式,那麽就需要使用expr之類的程序(見下文)。
除了只在程序內有效的壹般shell變量之外,還有環境變量。由export關鍵字處理的變量稱為環境變量。我們不討論環境變量,因為它們通常只在登錄腳本中使用。
外殼命令和過程控制
shell腳本中可以使用三種類型的命令:
1)Unix命令:
盡管可以在shell腳本中使用任意的unix命令,但是仍然有壹些相對更常用的命令。這些命令通常用於文件和文本操作。
常用命令的語法和功能
回顯“某些文本”:打印屏幕上的文本內容。
Ls:文件列表
Wc -l filewc -w filewc -c文件:計算文件的行數,計算文件的字數,計算文件的字符數。
Cp源文件destfile:文件副本
重命名或移動壹個文件。
Rm文件:刪除文件
Grep 'pattern' file:在文件中搜索字符串,例如grep 'searchstring' file.txt
Cut -b colnum file:指定要顯示的文件內容範圍,並輸出到標準輸出設備,例如輸出每行的第5到第9個字符。cut -b5-9 file.txt壹定不能和cat命令混淆,這是兩個完全不同的命令。
Cat file.txt:將文件內容輸出到標準輸出設備(屏幕)。
File somefile:獲取文件類型
讀取var:提示用戶輸入並將輸入賦給變量。
排序file.txt:對file.txt文件中的行進行排序。
Uniq:刪除文本文件中出現的行和列,比如sort file.txt | uniq。
Expr:執行數學運算示例:將2和3 expr 2“+”3相加。
查找:搜索文件,比如根據文件查找搜索查找。-名稱文件名-打印。
Tee:將數據輸出到標準輸出設備(屏幕)和文件,如somecommand | tee outfile。
Basename file:返回不帶路徑的文件名,例如basename /bin/tux將返回tux。
Dirname file:返回文件所在的路徑。比如dirname /bin/tux會返回/bin。
頭文件:打印文本文件的前幾行。
尾文件:打印文本文件的最後幾行。
Sed: Sed是壹個基本的查找和替換程序。您可以從標準輸入(如命令管道)讀取文本,並將結果輸出到標準輸出(屏幕)。該命令使用正則表達式(參見參考)進行搜索。不要將其與shell中的通配符混淆。例如,將linuxfocus替換為LinuxFocus: cattext.file | sed的/Linux focus/Linux focus/' > new text . file
Awk: awk用於從文本文件中提取字段。默認情況下,字段分隔符是壹個空格,您可以使用-F指定另壹個分隔符。Catfile。txt | awk-f,' {print $1 ',' $3} '在這裏用作字段分隔符,以同時打印第壹個和第三個字段。如果文件內容如下:Adam Bor,34,IndiaKerry Miller,22,USA command的輸出結果是Adam Bor,India Kerry Miller,USA。
2)概念:管道、重定向和backlog。
這些不是系統命令,但是它們真的很重要。
管道(|)將壹個命令的輸出作為另壹個命令的輸入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含“hello”的行,統計行數。
這裏,grep命令的輸出被用作wc命令的輸入。當然,您可以使用多個命令。
重定向:將命令的結果輸出到壹個文件,而不是標準輸出(屏幕)。
& gt寫入文件並覆蓋舊文件。
& gt& gt添加到文件的末尾,並保留舊文件的內容。
反斜杠
使用反斜杠將壹個命令的輸出用作另壹個命令的命令行參數。
命令:
找到。-mtime -1型f -print
用於查找在過去24小時內修改過的文件(-mtime–2表示過去48小時)。如果您想打包所有找到的文件,您可以使用以下腳本:
#!/bin/sh
#記號是反記號(`),而不是正常的引號('):
塔爾-zcvf lastmod.tar.gz `發現。-mtime -1型f -print `型
3)過程控制
“if”表達式如果條件為真,則執行以下部分:
如果....;然後
....
否則如果....;然後
....
其他
....
船方不負擔裝貨費用
在大多數情況下,您可以使用測試命令來測試條件。比如可以比較字符串,判斷文件是否存在,是否可讀等等。
通常,“[]”用來表示條件測試。註意這裏的空格是很重要的。確保方括號中的空格。
[-f "somefile"]:判斷是否是文件。
[-x "/bin/ls"]:判斷/bin/ls是否存在,是否有可執行權限。
[-n "$var"]:判斷$var變量是否有值。
["$a" = "$b"]:判斷$a和$b是否相等。
執行man test,查看所有測試表達式可以比較和判斷的類型。
直接執行以下腳本:
#!/bin/sh
if[" $ SHELL " = "/bin/bash "];然後
echo“您的登錄shell是bash (bourne again shell)”
其他
echo“您的登錄shell不是bash而是$SHELL”
船方不負擔裝貨費用
變量$SHELL包含登錄SHELL的名稱,我們將它與/bin/bash進行了比較。
快捷操作符
熟悉C語言的朋友可能會喜歡下面這個表達式:
[-f "/etc/shadow "]& amp;& ampecho“這臺計算機使用影子密碼”
這裏& amp;是快捷操作符,如果左邊的表達式為真,則執行右邊的語句。妳也可以把它想成邏輯運算中的壹個AND運算。在上面的示例中,如果/etc/shadow文件存在,將顯示“這臺計算機使用影子密碼”。同樣的OR操作(||)也可以在shell編程中使用。這裏有壹個例子:
#!/bin/sh
mail folder =/var/spool/mail/James
[-r " $ mail folder "]“{ echo”無法讀取$ mail folder ";出口1;}
echo "$mailfolder有郵件來自:"
grep " ^from " $郵件文件夾
該腳本首先確定mailfolder是否可讀。如果可讀,打印文件中的“From”行。如果它不可讀,則OR操作生效,腳本在打印錯誤消息後退出。這裏有壹個問題,就是我們必須有兩個訂單:
-打印錯誤信息
-退出程序
我們使用花括號將兩個命令以匿名函數的形式放在壹起作為壹個命令。下面將提到壹般功能。
沒有and和or操作符,我們可以對if表達式做任何事情,但是使用AND或OR操作符要方便得多。
Case表達式可用於匹配給定的字符串,而不是數字。
情況...在
...)在這裏做點什麽;;
environmental systems applications center 環境系統應用程序中心
讓我們看壹個例子。file命令可以識別給定文件的文件類型,例如:
lf.gz檔案
這將返回:
lf.gz: gzip壓縮數據,縮小,原文件名,
最後修改時間:2006年8月27日星期壹23:09:18
我們用這個寫壹個名為smartzip的腳本,可以自動提取bzip2、gzip和zip類型的壓縮文件:
#!/bin/sh
ftype = ` file " $ 1 " ` 0
案例" $ftype "在
" $1: Zip存檔" *)
解壓“$ 1”;;
" $1: gzip compressed"*)
gunzip " $ 1 ";;
" $1: bzip2 compressed"*)
bunzip2 " $ 1 ";;
*)錯誤“文件$1不能用smartzip解壓縮”;;
environmental systems applications center 環境系統應用程序中心
您可能註意到我們在這裏使用了壹個特殊的變量$1。該變量包含傳遞給程序的第壹個參數值。也就是說,當我們運行時:
smartzip articles.zip
$1是字符串articles.zip
Select expression是bash的擴展應用,尤其擅長交互使用。用戶可以從壹組不同的值中進行選擇。
選擇變量於...;做
破裂
完成的
....現在可以使用$var....
這裏有壹個例子:
#!/bin/sh
echo“妳最喜歡的操作系統是什麽?”
在“Linux”“Gnu Hurd”“Free BSD”“其他”中選擇var做
破裂
完成的
回顯“您選擇了$var”
以下是運行該腳本的結果:
妳最喜歡的操作系統是什麽?
1) Linux
2) Gnu赫德
3)免費BSD
4)其他
#?1
您選擇了Linux
您還可以在shell中使用以下循環表達式:
在…期間...;做
....
完成的
While-loop將壹直運行,直到表達式測試為真。當我們測試的表達式為真時將運行。關鍵字“break”用於跳出循環。關鍵字“continue”用於跳到下壹個循環,而不執行其余部分。
for循環表達式查看字符串列表(由空格分隔的字符串),並將它們賦給壹個變量:
為了改變....;做
....
完成的
在下面的例子中,ABC將分別被打印到屏幕上:
#!/bin/sh
用於在壹個B C中變化;做
echo“var is $ var”
完成的
下面是壹個比較有用的腳本showRPM,它的作用是打印壹些RPM包的統計數據:
#!/bin/sh
#列出壹些RPM包的內容摘要
#用法:showrpm rpmfile1 rpmfile2...
#示例:showrpm /cdrom/RedHat/RPMS/*。每分鐘轉數
對於$*中的rpmpackage做
if[-r " $ rpmpackage "];然後
echo " = = = = = = = = = = = = = = = $ rpmpackage = = = = = = = = = = = = = = "
rpm-qi-p $ rpm包
其他
echo "錯誤:無法讀取文件$rpmpackage "
船方不負擔裝貨費用
完成的
第二個特殊變量$ *出現在這裏,它包含所有輸入命令行參數值。如果運行showrpm OpenSSH。rpmw3m.rpmwebgrep.rpm
此時$ *包含三個字符串,即OpenSSH。rpm,W3M。rpm和Webgrep。轉速。
引用
在向程序傳遞任何參數之前,程序會擴展通配符和變量。這裏所謂的擴展名,是指程序將通配符(如*)替換為合適的文件名,其他變量替換為變量值。為了防止程序進行這種替換,妳可以使用引號:讓我們看壹個例子,假設在當前目錄中有壹些文件,兩個jpg文件,mail.jpg和tux.jpg。
#!/bin/sh
回聲*。使用jpeg文件交換格式存儲的編碼圖像文件擴展名
這將打印出“mail.jpg tux.jpg”的結果。
引號(單引號和雙引號)將防止通配符擴展:
#!/bin/sh
回聲" *。jpg "
回聲*。' jpg '
這將打印“*”。jpg”兩次。
單引號更嚴格。它可以防止任何變量擴大。雙引號可以防止通配符擴展,但允許變量擴展。
#!/bin/sh
echo $SHELL
回顯" $SHELL "
回顯' $SHELL '
運行結果如下:
/bin/bash
/bin/bash
$SHELL
最後,還有壹種方法可以防止這種擴展,那就是使用轉義符——反對角線:
回聲*。使用jpeg文件交換格式存儲的編碼圖像文件擴展名
echo $SHELL
這將輸出:
*.使用jpeg文件交換格式存儲的編碼圖像文件擴展名
$SHELL
此處文檔
在這裏,documents是向命令傳遞幾行文本的好方法。為每個劇本寫壹段有幫助的文字是非常有用的。這時,如果我們有那個here文檔,就不需要用echo函數逐行輸出了。“這裏的文檔”以
#!/bin/sh
#我們只有不到3個論點。打印幫助文本:
if[$ #-lt 3];然後
cat & lt& lt幫助
ren -使用sed正則表達式重命名多個文件
用法:ren 'regexp' 'replacement '文件...
示例:重命名所有*。HTM檔案在*。html:
任' HTM$' 'html' *。html文件的後綴
幫助
出口0
船方不負擔裝貨費用
OLD="$1 "
NEW="$2 "
shift命令從列表中刪除壹個參數
#命令行參數。
變化
變化
# $*現在包含所有文件:
對於$*中的文件;做
if[-f " $ file "];然後
NEW file = ` echo " $ file " | sed " s/$ { OLD }/$ { NEW }/g " ` 1
if[-f " $ new file "];然後
echo“錯誤:$newfile已經存在”
其他
回顯“將$file重命名為$newfile ...”
mv "$file" "$newfile "
船方不負擔裝貨費用
船方不負擔裝貨費用
完成的
這是壹個更復雜的例子。下面詳細討論壹下。第壹個if表達式確定輸入命令行參數是否小於3(特殊變量$ #表示參數的數量)。如果輸入參數小於3,幫助文本將被傳遞給cat命令,然後由cat命令打印在屏幕上。程序在打印幫助文本後退出。如果輸入參數等於或大於3,我們將把第壹個參數賦給變量OLD,把第二個參數賦給變量NEW。接下來,我們使用shift命令從參數列表中刪除第壹個和第二個參數,這樣原來的第三個參數就變成了參數列表$ *中的第壹個參數。然後我們開始循環,命令行參數列表被逐個賦給變量$file。然後我們判斷文件是否存在,如果存在,搜索並用sed命令替換,生成新的文件名。然後將反斜杠中的命令結果賦給newfile。這樣,我們達到了目的:我們得到了舊文件名和新文件名。然後使用mv命令將其重命名。
功能
如果妳寫壹個稍微復雜壹點的程序,妳會發現在程序的幾個地方可能會用到同壹個代碼,妳也會發現如果我們用函數的話會方便很多。壹個函數看起來像這樣:
函數名()
{
#在函數體$1中,是函數的第壹個參數
# $2秒...
身體
}
妳需要在每個程序的開始聲明這個函數。
下面是壹個名為xtitlebar的腳本,使用它可以更改終端窗口的名稱。這裏使用了壹個名為help的函數。如您所見,這個定義的函數使用了兩次。
#!/bin/sh
# vim: set sw=4 ts=4 et:
幫助()
{
cat & lt& lt幫助
修改壹個xterm,gnome終端或者kde konsole的名字
用法:xtitlebar[-h]" string _ for _ titel bar "
選項:-h幫助文本
例如:xtitlebar“CVS”
幫助
出口0
}
#如果出現錯誤或給定了-h,我們調用函數help:
[-z " $ 1 "]& amp;& amp幫助
[" $ 1 " = "-h "]& amp;& amp幫助
#發送轉義序列以更改xterm titelbar:
echo-e " 33]0;$107"
#
在腳本中提供幫助是壹個很好的編程習慣,可以方便其他用戶(和您)使用和理解腳本。
命令行參數
我們已經看到了特殊變量,如$ *和$1,$2...$9,這些特殊變量包含用戶從命令行輸入的參數。到目前為止,我們只學習了壹些簡單的命令行語法(比如壹些強制參數和查看幫助的-h選項)。但是當編寫更復雜的程序時,妳可能會發現妳需要更多的定制選項。通常的做法是在所有可選參數前加壹個減號,後面跟參數值(比如文件名)。
有許多方法可以分析輸入參數,但是下面使用case表達式的例子是壹個很好的例子。
#!/bin/sh
幫助()
{
cat & lt& lt幫助
這是壹個通用的命令行解析器演示。
用法示例:cmd parser-l hello-f--some file 1 some file 2
幫助
出口0
}
while[-n " $ 1 "];做
案例$1英寸
幫助;shift 1;;#調用函數幫助
-f)opt _ f = 1;shift 1;;#變量opt_f已設置
-l)opt _ l = $ 2;班次2;;# -l接受壹個參數-& gt;移動2
-)移位;打破;;#選項結束
-*) echo”錯誤:沒有這樣的選項$1。-h表示幫助";出口1;;
*)破;;
environmental systems applications center 環境系統應用程序中心
完成的
echo“opt _ f是$opt_f”
echo“opt _ l是$opt_l”
echo "第壹個參數是$1 "
echo "第二個參數是$2 "
您可以像這樣運行腳本:
cmd parser-l hello-f--some file 1 some file 2
返回的結果是:
opt_f是1
opt_l是hello
第壹個參數是-somefile1
第二個參數是somefile2
這個腳本是如何工作的?該腳本首先遍歷所有輸入命令行參數,將輸入參數與case表達式進行比較,設置壹個變量,如果匹配就刪除參數。按照unix系統的約定,應該先輸入包含負號的參數。
例子
壹般編程步驟
現在我們來討論壹下寫劇本的壹般步驟。任何優秀的腳本都應該有幫助和輸入參數。並且編寫壹個偽腳本(framework.sh)是壹個非常好的想法,它包含了大多數腳本需要的框架結構。這時,在編寫新的腳本時,我們只需要執行copy命令:
cp framework.sh myscript
然後插入自己的函數。
讓我們再看兩個例子:
二進制到十進制的轉換
腳本b2d將二進制數(如1101)轉換成相應的十進制數。這也是使用expr命令進行數學運算的壹個示例:
#!/bin/sh
# vim: set sw=4 ts=4 et:
幫助()
{
cat & lt& lt幫助
b2h -將二進制轉換為十進制
用法:b2h [-h] binarynum
選項:-h幫助文本
例如:b2h 111010
將返回58
幫助
出口0
}
錯誤()
{
#打印錯誤並退出
echo "$1 "
1號出口
}
lastchar()
{
#返回$rval中字符串的最後壹個字符
if[-z " $ 1 "];然後
#空字符串
rval= " "
返回
船方不負擔裝貨費用
# wc在輸出後面留出壹些空間,這就是我們需要sed的原因:
numofchar = ` echo-n " $ 1 " | WC-c | sed ' s///g ' ` 1
#現在去掉最後壹個字符
rval = ' echo-n " $ 1 " | cut-b $ numofchar '
}
斬()
{
#刪除字符串中的最後壹個字符,並在$rval中返回
if[-z " $ 1 "];然後
#空字符串
rval= " "
返回
船方不負擔裝貨費用
# wc在輸出後面留出壹些空間,這就是我們需要sed的原因:
numofchar = ` echo-n " $ 1 " | WC-c | sed ' s///g ' ` 1
if[" $ numofchar " = " 1 "];然後
#字符串中只有壹個字符
rval= " "
返回
船方不負擔裝貨費用
numofcharminus 1 = ' expr $ numofchar "-" 1 '
#現在除了最後壹個字符之外,刪除所有字符:
rval = ` echo-n " $ 1 " | cut-b 0-$ { numofcharminus 1 } ` 1
}
while[-n " $ 1 "];做
案例$1英寸
幫助;shift 1;;#調用函數幫助
-)移位;打破;;#選項結束
-*) error”錯誤:沒有這樣的選項$1。-h表示幫助";;
*)破;;
environmental systems applications center 環境系統應用程序中心
完成的
#主程序
總和=0
重量=1
#必須給定壹個參數:
[-z " $ 1 "]& amp;& amp幫助
binnum="$1 "
binnumorig="$1 "
while[-n " $ binnum "];做
lastchar "$binnum "
if[" $ rval " = " 1 "];然後
sum=`expr "$weight" "+" "$sum " `
船方不負擔裝貨費用
#刪除$binnum中的最後壹個位置
印章" $binnum "
binnum="$rval "
weight = `expr " $ weight " " * " 2 '
完成的
echo "二進制$binnumorig是十進制$sum "
#
該腳本中使用的算法是使用十進制和二進制權重(1,2,4,8,16,...),例如二進制“10”可以這樣轉換成十進制:
0 * 1 + 1 * 2 = 2
為了得到壹個二進制數,我們使用lastchar函數。這個函數使用WC–C計算字符數,然後使用cut命令取出最後壹個字符。Chop函數是刪除最後壹個字符。
文件循環程序
也許妳是想把所有發來的郵件都存到壹個文件裏的人之壹,但是幾個月後,文件可能會變得很大,以至於對文件的訪問速度會變慢。下面的腳本rotatefile可以解決這個問題。這個腳本可以將郵件保存文件(假設為outmail)重命名為outmail.1,對於outmail.1,則變成outmail.2,以此類推。...
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1 "
幫助()
{
cat & lt& lt幫助
rotatefile -旋轉文件名
用法:rotatefile [-h] filename
選項:-h幫助文本
示例:rotatefile out
例如,這會將out.2重命名為out.3,將out.1重命名為out.2,將out重命名為out.1
並創建壹個空的輸出文件
最大數量是10
版本$ver
幫助
出口0
}
錯誤()
{
echo "$1 "
1號出口
}
while[-n " $ 1 "];做
案例$1英寸
幫助;shift 1;;
-)破;;
-*) echo”錯誤:沒有這樣的選項$1。-h表示幫助";出口1;;
*)破;;
environmental systems applications center 環境系統應用程序中心
完成的
#輸入檢查:
if[-z " $ 1 "];然後
錯誤“錯誤:您必須指定壹個文件,使用-h獲取幫助”
船方不負擔裝貨費用
filen="$1 "
#重命名任何. 1、. 2等文件:
對於9 8 7 6 5 4 3 2 1中的n;做
if [ -f "$filen。$ n "];然後
p= '表達式$n + 1 '
回聲”mv $filen。$n $filen。$p "
mv $filen。$n $filen。$p
船方不負擔裝貨費用
完成的
#重命名原始文件:
if[-f " $ filen "];然後
echo "mv $filen $filen.1 "
mv $filen $filen.1
船方不負擔裝貨費用
echo touch $filen
觸摸$filen
這個腳本是如何工作的?在檢測到用戶提供了文件名後,我們從9到1進行了壹次循環。文件9被命名為10,文件8被重命名為9,依此類推。循環完成後,我們將原始文件命名為file 1,並創建壹個與原始文件同名的空文件。
試運行測試/調試
最簡單的調試命令當然是使用echo命令。您可以使用echo在您懷疑有問題的地方打印任何變量值。這也是為什麽大多數shell程序員花80%的時間調試程序。Shell程序的好處是不需要重新編譯,插入壹個echo命令也不需要太多時間。
Shell也有真正的調試模式。如果腳本“strangescript”中有錯誤,可以這樣調試:
sh -x奇異腳本
這將執行腳本並顯示所有變量的值。
shell還有壹種模式,在這種模式下,您不需要執行腳本,只需檢查語法。妳可以這樣使用它:
妳的腳本
這將返回所有語法錯誤。