在學習Linux的過程當中,無可避免的會碰到一個既讓人喜歡、又使人十分頭疼的神奇的東西——bash編程,也就是shell腳本。那麼什麼是shell腳本呢?shell是一個命令語言解釋器,而shell腳本則是Linux命令的集合,按照預設的順序依次解釋執行,來完成特定的、較複雜的系統管理任務,相似於windows中的批處理文件。本篇博文主要介紹bash編程的基礎語法講解。算法
本地變量:只對當前shelll進程有效的變量,對其餘shell進程無效,包含當前shell進程的子進程。shell
即向變量的存儲空間保存數據,以下編程
[root@localhost ~]# VAR_NAME=VALUE
格式爲:${VAR_NAME}windows
" ":弱引用,裏面的變量會被替換; ' ':強引用,裏面的全部字符都是字面量,直接輸出,所見即所得;
對當前shell進程及其子shell有效,對其餘的shell進程無效!bash
定義:VAR_NAME=VALUE 導出:export VAR_NAME 撤銷變量:unset VAR_NAME 只讀變量:readonly VAR_NAME
在shell腳本中定義,只能夠在shell腳本中使用!網絡
$1,$2...,${10}
shell對一些參數作特殊處理,這些參數只能被引用而不能被賦值!ide
$#:傳遞到腳本的參數個數 $*:顯示全部向腳本傳遞的參數 //與位置變量不一樣,此選項參數可超過9個 $$:獲取當前shell的進程號 $!:執行上一個指令的進程號 $?:獲取執行的上一個指令的返回值 //0爲執行成功,非零爲執行失敗 $-:顯示shell使用的當前選項,與set命令功能相同 $@ 與$*相同,可是使用時加引號,並在引號中返回每一個參數
set:查看當前shell進程中的全部變量; export、printenv、env:查看當前shell進程中的全部環境變量;
1)不能使用程序中的關鍵字; 2)只能使用數字、字母和下劃線,不可以使用數字開頭; 3)系統變量默認都是大寫,自定義變量儘可能不要與系統變量衝突; 4)儘可能作到見名知意;
1)數值型:精確數值(整數)、近似數值(浮點數); 2)字符型:char、string; 3)布爾型:true、false;
1)顯示轉換; 2)隱式轉換;
功能:設定本地變量、定義命令別名。函數
profile類:爲交互式登陸的用戶提供配置!學習
全局:/etc/profile、/etc/profile.d/*.sh 用戶:~/.bash_profile
bashrc類:爲非交互式的用戶提供配置!測試
全局:/etc/bashrc 用戶:~/.bashrc
shell腳本第一行必須頂格寫,用shabang定義指定的解釋器來解釋該腳本。
#!/bin/bash //!即爲shebang //其它的以#開頭的行均爲註釋,會被解釋器忽略,可用來註釋腳本用途及版本,方便使用管理。
bash編程屬於面向過程編程,執行方式以下:
1)順序執行:按命令前後順尋依次執行; 2)選擇執行:測試條件,可能會多個測試條件,某條件知足時,則執行對應的分支; 3)循環執行:將同一段代碼反覆執行屢次,所以,循環必須有退出條件;不然,則陷入死循環;
1)bash -n SHELLNAME #語法測試,測試是否存在語法錯誤; 2)bash -x SHELLNAME #模擬單步執行,顯示每一步執行過程;
定義整型變量:
1)et VAR_NAME=INTEGER_VALUE //例如:let a=3 2)declare -i VAR_NAME=INTEGER_VALUE //例如:declare -i a=3
實現算術運算的方式:
let VAR_NAME=ARITHMATIC_EXPRESSION VAR_NAME=$[ARITHMATIC_EXRESSION] VAR_NAME=$((EXPRESSION)) VAR_NAME=$(expr $num1 + $num2)
算法運算符:
+:加法 -:減法 *:乘法 /:整除 %:取餘數 **:乘冪
注意:即便沒有定義爲整型變量,字符型的數字依然能夠參與算術運算,bash會執行變量類型的隱式類型轉換。
布爾運算:真,假 與運算:真 && 真 = 真 真 && 假 = 假 假 && 真 = 假 假 && 假 = 假 或運算:真 || 真 = 真 真 || 假 = 真 假 || 真 = 真 假 || 假 = 假 非運算:!真=假 !假=真
整型測試:整數比較
例如 [ $num1 -gt $num2 ] -gt: 大於 -lt: 小於 -ge: 大於等於 -le: 小於等於 -eq: 等於 -ne: 不等於
字符測試:字符串比較
雙目: 例如[[ "$str1" > "$str2" ]] >: 大於則爲真 <: 小於則爲真 >=:大於等於則爲真 <=:小於等於則爲真 ==:等於則爲真 !=:不等於則爲真 單目: -n String: 是否不空,不空則爲真,空則爲假 -z String: 是否爲空,空則爲真,不空則假 文件測試:判斷文件的存在性及屬性等 -a FILE:存在則爲真;不然則爲假; -e FILE:存在則爲真;不然則爲假; -f FILE: 存在而且爲普通文件,則爲真;不然爲假; -d FILE: 存在而且爲目錄文件,則爲真;不然爲假; -L/-h FILE: 存在而且爲符號連接文件,則爲真;不然爲假; -b: 存在而且爲塊設備,則爲真;不然爲假; -c: 存在而且爲字符設備,則爲真;不然爲假 -S: 存在而且爲套接字文件,則爲真;不然爲假 -p: 存在而且爲命名管道,則爲真;不然爲假 -s FILE: 存在而且爲非空文件則爲值,不然爲假; -r FILE:文件可讀爲真,不然爲假 -w FILE:文件可寫爲真,不然爲假 -x FILE:文件可執行爲真,不然爲假 file1 -nt file2: file1的mtime新於file2則爲真,不然爲假; file1 -ot file2:file1的mtime舊於file2則爲真,不然爲假; 組合條件測試:在多個條件間實現邏輯運算 與:[ condition1 -a condition2 ] condition1 && condition2 或:[ condition1 -o condition2 ] condition1 || condition2 非:[ -not condition ] ! condition 與:COMMAND1 && COMMAND2 COMMAND1若是爲假,則COMMAND2不執行 或:COMMAND1 || COMMAND2 COMMAND1若是爲真,則COMMAND2不執行 非:! COMMAND
一、if語句之單分支 語句結構: if 測試條件;then 選擇分支 fi 表示條件測試狀態返回值爲值,則執行選擇分支 例:寫一個腳本,接受一個參數,這個參數是用戶名;若是此用戶不存在,則建立該用戶; #!/bin/bash if ! id $1 &> /dev/null;then useradd $1 fi 二、if語句之雙分支 語句結構: if 測試條件;then 選擇分支1 else 選擇分支2 fi 兩個分支僅執行其中之一 例:經過命令行給定一個文件路徑,然後判斷:若是此文件中存在空白行,則顯示其空白行的總數;不然,則顯示無空白行; #!/bin/bash if grep "^[[:space]]*$" $1 &> /dev/null; then echo "$1 has $(grep "^[[:space]]*$" $1 | wc -l) blank lines." else echo "No blank lines" fi 注意:若是把命令執行成功與否看成條件,則if語句後必須只跟命令自己,而不能引用。 三、if語句之多分支 語句結構: if 條件1;then 分支1 elif 條件2;then 分支2 elif 條件3;then 分支3 ... else 分支n fi 例:傳遞一個用戶名給腳本:若是此用戶的id號爲0,則顯示說這是管理員;若是此用戶的id號大於等於500,則顯示說這是普通用戶;不然,則說這是系統用戶。 #!/bin/bash if [ $# -lt 1 ]; then echo "Usage: `basename $0` username" exit 1 fi if ! id -u $1 &> /dev/null; then echo "Usage: `basename $0` username" echo "No this user $1." exit 2 fi if [ $(id -u $1) -eq 0 ]; then echo "Admin" elif [ $(id -u $1) -ge 500 ]; then echo "Common user." else echo "System user." fi
read [option] 「prompt」-p:直接指定一個變量接受參數 -t timaout:指定等待接受參數的時間 -n:表示不換行 例:輸入用戶名,可返回其shell #!/bin/bash read -p "Plz input a username: " userName if id $userName &> /dev/null; then echo "The shell of $userName is `grep "^$userName\>" /etc/passwd | cut -d: -f7`." else echo "No such user. stupid." fi
case語句:有多個測試條件時,case語句會使得語法結構更明晰 語句結構: case 變量引用 in PATTERN1) 分支1 ;; PATTERN2) 分支2 ;; ... *) 分支n ;; esac
PATTERN:類同於文件名通配機制,但支持使用|表示或者
a|b: a或者b*:匹配任意長度的任意字符 ?: 匹配任意單個字符 []: 指定範圍內的任意單個字符 例:寫一個腳本,完成以下任務,其使用形式以下所示: script.sh {start|stop|restart|status} 其中:若是參數爲空,則顯示幫助信息,並退出腳本; 若是參數爲start,則建立空文件/var/lock/subsys/script,並顯示「starting script successfully.」 若是參數爲stop,則刪除文件/var/lock/subsys/script,並顯示「Stop script successfully.」 若是參數爲restart,則刪除文件/var/locksubsys/script並從新建立,然後顯示「Restarting script successfully.」 若是參數爲status,那麼:若是文件/var/lock/subsys/script存在,則顯示「Script is running…」,不然,則顯示「Script is stopped.」 #!/bin/bash file='/var/lock/subsys/script' case $1 in start) if [ -f $file ];then echo "Script is running..." exit 3 else touch $file [ $? -eq 0 ] && echo "Starting script successfully." fi ;; stop) if [ -f $file ];then rm -rf $file [ $? -eq 0 ] && echo "Stop script successfully." else echo "Script is stopped..." exit 4 fi ;; restart) if [ -f $file ];then rm -rf $file [ $? -eq 0 ] && echo "Stop script successfully" else echo "Script is stopped..." exit 5 fi touch $file [ $? -eq 0 ] && echo "Starting script successfully" ;; status) if [ -f $file ];then echo "Script is running..." else echo "Script is stopped." fi ;; *) echo "`basename $0` {start|stop|restart|status}" exit 2 ;; esac
一、for語句格式一 語句結構: for 變量名 in 列表; do 循環體 done 列表:可包含一個或多個元素 循環體:依賴於調用變量來實現其變化 循環可嵌套 退出條件:遍歷元素列表結束 例:求100之內全部正整數之和 #!/bin/bash declare -i sum=0 for i in {1..100}; do let sum+=$i done echo $sum 二、for語句格式二 for ((初始條件;測試條件;修改表達式)); do 循環體 done 先用初始條件和測試條件作判斷,若是符合測試條件則執行循環體,再修改表達式,不然直接跳出循環。 例:求100之內全部正整數之和(for二實現) #!/bin/bash declare -i sum=0 for ((counter=1;$counter <= 100; counter++)); do let sum+=$counter done echo $sum
while循環語句適用於循環次數未知,或不適用for直接生成較大的列表!
語句結構: while 測試條件; do 循環體 done 測試條件爲真,進入循環;測試條件爲假,退出循環 例1:求100之內全部偶數之和,要求使用取模方法 #!/bin/bash declare -i counter=1 declare -i sum=0 while [ $counter -le 100 ]; do if [ $[$counter%2] -eq 0 ]; then let sum+=$counter fi let counter++ done echo $sum 例2:提示用戶輸入一個用戶名,若是用戶存在,就顯示用戶的ID號和shell;不然顯示用戶不存在;顯示完成以後不退出,再次重複前面的操做,直到用戶輸入q或quit爲止 #!/bin/bash read -p "Plz enter a username: " userName while [ "$userName" != 'q' -a "$userName" != 'quit' ]; do if id $userName &> /dev/null; then grep "^$userName\>" /etc/passwd | cut -d: -f3,7 else echo "No such user." fi read -p "Plz enter a username again: " userName done while特殊用法:遍歷文本文件 語句結構: while read 變量名; do 循環體 done < /path/to/somefile 變量名,每循環一次,記憶了文件中一行文本 例:顯示ID號爲偶數,且ID號同GID的用戶的用戶名、ID和SHELL while read line; do userID=`echo $line | cut -d: -f3` groupID=`echo $line | cut -d: -f4` if [ $[$userID%2] -eq 0 -a $userID -eq $groupID ]; then echo $line | cut -d: -f1,3,7 fi done < /etc/passwd
語句結構: until 測試條件; do 循環體 done 測試條件爲假,進入循環;測試條件爲真,退出循環 例:求100之內全部偶數之和,要求使用取模方法(until實現) #!/bin/bash declare -i counter=1 declare -i sum=0 until [ $counter -gt 100 ]; do if [ $[$counter%2] -eq 0 ]; then let sum+=$counter fi let counter++ done echo $sum 例:提示用戶輸入一個用戶名,若是用戶存在,就顯示用戶的ID號和shell;不然顯示用戶不存在;顯示完成以後不退出,再次重複前面的操做,直到用戶輸入q或quit爲止(until實現) #!/bin/bash read -p "Plz enter a username: " userName until [ "$userName" = 'q' -a "$userName" = 'quit' ]; do if id $userName &> /dev/null; then grep "^$userName\>" /etc/passwd | cut -d: -f3,7 else echo "No such user." fi read -p "Plz enter a username again: " userName done
循環控制命令: 1)break:提早退出循環; 2)break [N]: 退出N層循環;N省略時表示退出break語句所在的循環; 3)continue: 提早結束本輪循環,而直接進入下輪循環; 4)continue [N]:提早第N層的循環的本輪循環,而直接進入下輪循環;
#while體while true; do 循環體 done #until體 until false; do 循環體 done 例1:寫一個腳本,判斷給定的用戶是否登陸了當前系統 (1) 若是登陸了,則腳本終止; (2) 每5秒種,查看一次用戶是否登陸; #!/bin/bash while true; do who | grep "gentoo" &> /dev/null if [ $? -eq 0 ];then break fi sleep 5 done echo "gentoo is logged."
位置參數能夠用shift命令左移,好比shift 3 表示原來的$4如今變成$1,原來的$5變成$2等等,原來的$一、$二、$3丟棄,$0不移動。不帶參數的shift命令至關於shift 1。
咱們知道,對於位置變量或命令行參數,其個數必須是肯定的,或者當shell程序不知道其個數時,能夠把全部參數一塊兒複製給「$*」。若用戶要求 Shell 在不知道位置變量個數的狀況下,還能逐個的把參數一一處理,也就是在 $1 後爲 $2,在 $2 後面爲 $3 等。在 shift 命令執行前變量 $1 的值在 shift 命令執行後就不可用了。
實例一以下:
[root@localhost ~]# cat 1.sh #!/bin/bash while [ $# -ne 0 ] do echo "第一個參數爲:$1 參數個數爲:$#" shift done [root@localhost ~]# sh 1.sh 1 2 3 4 第一個參數爲:1 參數個數爲:4 第一個參數爲:2 參數個數爲:3 第一個參數爲:3 參數個數爲:2 第一個參數爲:4 參數個數爲:1
從上面例子中能夠看出shift命令每執行一次,變量的個數($#)減1,而變量的值提早一位。
實例二以下:
[root@localhost ~]# cat 2.sh #!/bin/bash if [ $# -eq 0 ] then echo "Usage:2.sh 參數" exit 1 fi sum=0 while [ $# -ne 0 ] do sum=`expr ${sum} + $1` shift done echo "sum is:${sum}" [root@localhost ~]# sh 2.sh 10 20 30 sum is:60
shift命令還有一個重要用途,Bash定義了9個位置變量,從$1到$9,這並不意味這用戶在命令行只能使用9個參數,藉助shift命令能夠訪問多於9個的參數。
shift命令一次移動到參數的個數由其所帶的參數指定,例如當shell程序處理完前9個命令行參數後,可使用shift 9命令把$10移動到$1。
語法結構:
function F_NAME { 函數體 } 或 F_NAME() { 函數體 }
可調用:使用函數名,函數名出現的地方,會被自動替換爲函數; 函數的返回值: 函數的執行結果返回值:代碼的輸出 函數中使用打印語句:echo, printf 函數中調用的系統命令執行後返回的結果 執行狀態返回值: 默認取決於函數體執行的最後一個命令狀態結果 自定義退出狀態碼:return [0-255]
注意:函數體運行時,一旦遇到return語句,函數即返回!
在函數中調用函數參數的方式同腳本中調用腳本參數的方式:
位置參數 $1, $2, … $#, $*, $@
實例:
要求以下:
1)提示用戶輸入一個可執行命令;
2)獲取這個命令所依賴的全部庫文件(使用ldd命令);
3)複製命令之/mnt/sysroot目錄;
4)複製各庫文件至/mnt/sysroot對應的目錄中;
[root@localhost ~]# cat 1.sh #!/bin/bash target=/mnt/sysroot/ [ -d $target ] || mkdir $target preCommand() { if which $1 &> /dev/null; then commandPath=`which --skip-alias $1` return 0 else echo "No such command." return 1 fi } commandCopy() { commandDir=`dirname $1` [ -d ${target}${commandDir} ] || mkdir -p ${target}${commandDir} [ -f ${target}${commandPath} ] || cp $1 ${target}${commandDir} } libCopy() { for lib in `ldd $1 | egrep -o "/[^[:space:]]+"`; do libDir=`dirname $lib` [ -d ${target}${libDir} ] || mkdir -p ${target}${libDir} [ -f ${target}${lib} ] || cp $lib ${target}${libDir} done } read -p "Plz enter a command: " command until [ "$command" == 'quit' ]; do if preCommand $command &> /dev/null; then commandCopy $commandPath libCopy $commandPath fi exit 1 done [root@localhost ~]# sh 1.sh Plz enter a command: cat [root@localhost ~]# ls /mnt/sysroot/bin/ cat [root@localhost ~]# ls /mnt/sysroot/ bin lib64
trap命令用於在shell程序中捕捉到信號,以後能夠由三種反應方式:
1)執行一段程序來處理這一信號;
2)接收信號的默認操做;
3)忽略這一信號;
示例:
寫一個腳本,可以ping探測指定網絡內的全部主機是否在線,當沒有執行完時可接收ctrl+c命令退出。
[root@localhost ~]# cat 1.sh #!/bin/bash quitScript() { echo "Quit..." } trap 'quitScript; exit 5' SIGINT cnetPing() { for i in {1..254}; do if ping -c 1 -W 1 $1.$i &> /dev/null; then echo "$1.$i is up." else echo "$1.$i is down." fi done } bnetPing() { for j in {0..255}; do cnetPing $1.$j done } anetPing() { for m in {0..255}; do bnetPing $1.$m done } netType=`echo $1 | cut -d"." -f1` if [ $netType -ge 1 -a $netType -le 126 ]; then anetPing $netType elif [ $netType -ge 128 -a $netType -le 191 ]; then bnetPing $(echo $1 | cut -d'.' -f1,2) elif [ $netType -ge 192 -a $netType -le 223 ]; then cnetPing $(echo $1 | cut -d'.' -f1-3) else echo "Wrong" exit 2 fi [root@localhost ~]# sh 1.sh 192.168.1.1 192.168.1.1 is down. 192.168.1.2 is down. 192.168.1.3 is down. 192.168.1.4 is down. 192.168.1.5 is down. 192.168.1.6 is down. 192.168.1.7 is down. 192.168.1.8 is down. 192.168.1.9 is down. 192.168.1.10 is up. 192.168.1.11 is down. ^CQuit...
——————————本次博文到此結束,感謝閱讀——————————