寫在前面:
博客書寫牢記5W1H法則:What,Why,When,Where,Who,How。
本篇主要內容:
● bash腳本格式
● 知識回顧
變量
數據類型
算數運算
賦值
隨機數
變量類型定義
數值
只讀
一維數組
● 條件測試
[ ]與` `
數值測試
字符串測試
文件測試
● 腳本執行結果返回值
● 位置參數變量
● 特殊變量
● 選擇執行
if語句
● 用戶交互、錯誤排查
read
bash -n
bash -x
bash腳本格式:
(1)第一行,頂格,定義解釋器#!/bin/bash
(2)以#開頭的行爲註釋信息,寫明腳本作用版本歷史版本等信息
(3)代碼區塊前段應該以#註明代碼作用,方便後期閱讀修改
(4)注意縮進,適度添加空白行
(5)bash中儘量定義PATH和locale變量,以保證命令引用和輸出正確。即定義PATH= LANG=
知識回顧:
變量:
局部變量:定義在腳本方法中,也只在其中有效。(鳥哥的書中本地變量和局部變量是同一個含義,都是自定義的變量)
本地變量:只在當前shell有效,不傳遞給子shell。直接定義的自定義變量。
環境變量:在當前shell有效,並會傳遞給子shell。用export定義或修改爲環境變量。
位置參數變量:在shell中可通過$#引用的參數字符
特殊變量:shell預先定義的有一定含義的字符。如$?返回上一條命令的執行結果返回值;$#返回參數個數等。
數據類型:
字符型、數值型。shell是弱類型編程語言,默認會將所有變量都當做字符型。
算數運算:
+,-,*,/,%,**
let VAR=EXPRESSION
VAR=$[ EXPRESSION ]
VAR=$(( EXPRESSION ))
VAR=$( expr ARGU1 運算符 ARGU2 ... )
注意:有些時候乘法符號需要轉義,如最後一種算數運算式。
增強型賦值:
變量進行算數運算後,又將結果回存至變量中:
let I=$I+#與下面等同
let I+=#
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自減:
VAR=$[$VAR-1]
let VAR-=1
let VAR--
隨機數:
$RANDOM會返回0-32767之間的整數。
利用$RANDOM取一定數值內的隨機數。
如取0-99之間隨機數:$RANDOM*99/32767即可啦。
變量類型定義:
declare [-aixr] VAR
數值:
declare -i SUM=100+200;echo $SUM 定義SUM爲數值類型
declare -x VAR 將VAR變爲環境變量,等同於export;恢復用 +x
只讀:
declare -r VAR 將VAR定義爲只讀,同readonly
一維數組(array):
數組定義:
(1)declare -a ARR=(VALUE0 VALUE1 VALUE2 ...) :使用declare定義並賦值數組
(2)declare -a ARR=($( COMMAND )) :使用命令引用爲數組賦值
(3)declare -a ARR=([0]=VALUE0 [2]=VALUE2 ...) :單獨爲每個數組單元賦值
(4)read -a ARR :使用read命令讀取用戶輸入的數組,空格間隔數組中單元
數組引用:
echo ${ARR[@]} :引用所有數組單元
echo ${ARR[#]} :#爲數字,引用某一數組單元
echo ${#ARR[@]} :#就是#,引用數組長度
echo ${array[@]:n:m} :引用數組第n-m個單元
echo ${!ARR[@]} :引用數組的所有下標
實例:
#獲取/etc/passwd文件總第18行和第15行用戶的UID,並計算其UID的和。
[root@localhost shell]# cat userIDsum.sh #!/bin/bash # read /etc/passwd file,then get 15th and 18th users UID,get SUM. # by fred # v 0.0.1 # 2016.3.14 # get UID for line 18 and line 15 UID_18=`cut -d: -f3 /etc/passwd | head -n 18 | tail -n1` UID_15=`cut -d: -f3 /etc/passwd | head -n 15 | tail -n1` # get SUM SUM=$[ $UID_15 + $UID_18 ] # print SUM echo $SUM
#計算/etc/rc.d/init.d/functions和/etc/inittab文件的空白行數之和
[root@localhost shell]# cat spaceline_SUM.sh #!/bin/bash # get spacelines SUM of file /etc/rc.d/init.d/functions and file /etc/rc.d/init.d/network # by fred # v 0.0.1 # 2016.3.14 # set filepath FILE1='/etc/rc.d/init.d/functions' FILE2='/etc/rc.d/init.d/network' # get spacelines of files FILE1_SPACELINE=`grep "^[[:space:]]*$" $FILE1 | wc -l` FILE2_SPACELINE=`grep "^[[:space:]]*$" $FILE2 | wc -l` # get sum SUM=$[ $FILE1_SPACELINE + $FILE2_SPACELINE ] echo "$SUM"
條件測試:
判斷某需求是否滿足,並有選擇的執行其他代碼內容。
(1)查看命令結果返回值:$?
0:成功
1-255:失敗
(2)測試表達式
test EXPRESSION 或 [ EXPRESSION ]
` EXPRESSION `
注意:兩端空格不能省略
[ ]與` `
參閱"help ["命令,可以得到以下內容:
"["是內嵌命令test的同義詞,必須以字符"]"結尾,以匹配開始的"["。
"[["會根據EXPRESSION的估值返回狀態0或1。EXPRESSION按照"test"的相同條件組成,也可以使用下列操作符連接:
( EXPRESSION ):返回EXPRESSION的值
! EXPRESSION:去反
EXPR1 && EXPR2:與
EXPR1 || EXPR2:或
總結:主要有一下不同點:
類別 | [ ] | ` ` |
獲取幫助 | man test | man bash 1700行左右 |
A=~B,A包含B | 不支持 | 支持=~ |
&&、|| 表示與或 | 不支持 | 支持 |
-a -o 表示與或 | 支持 | 不支持 |
-N,文件在最後讀之後被修改modified | 不支持 | 支持 |
其他,請自行 man |
數值測試:數值比較
-eq:等於
-ne:不等
-gt:大於
-lt:小於
-ge:大於等於
-le:小於等於
補充:man 1 test可以看到相關內容
字符串測試:
-v VARNAME:變量已定義則爲真
-z string:字符串長度爲0
string或-n string:字符串長度非0
string1 == string2或string1 = string2:=在test和[ ]語句中使用。
string1 != string2
string1 < string2:如果 string1 在當前語言環境的字典順序中排在 string2 之前則爲真。
string1 =~ string2:string1中包含string2則爲真
注意:字符串測試選擇使用` `或[],如“=~”的測試就只能在` `中正確執行;字符串最好在“”內,防止字符串爲空時導致的錯誤。字符串測試符號與字符串之間的空格必須保留。
文件測試:
存在與否:
-a:文件存在
-e:文件存在
類型:
-b:塊設備
-c:字符設備
-d:目錄
-f:普通文件
-h或-L:軟鏈接
-p:命名管道
-S:套接字
權限:
-r:可讀
-w:可寫
-x:可執行
特殊權限:
-u:特殊權限SUID已設置
-g:特殊權限SGID已設置
-k:特殊權限sticky已設置
文件內容:
-s:文件大小非0
從屬:
-G:已設置可用屬組
-O:已設置可用屬主
時間戳:
-N:最後讀取後有內容修改
雙目測試:
file1 -ef file2:兩文件指向同一文件系統的相同inode,即爲同一文件的硬鏈接
file1 -nt file2:fiel1比file2新,特指mtime。或file1存在,file2不存在
file1 -ot file2:file1比file2老,特指atime。或file1不存在,file2存在
組合測試:
邏輯運算:
(1)&& || !
(2)-a -o !
實例:
#但當前主機名爲空或爲localhost.localdomin時,將其設置爲www.fredme.com
[root@magedu ~]# cat hostname.sh #!/bin/bash # if hostname is null or "localhost.localdomin",then set hostname "www.fredme.com" # by fred # 0.0.1 # 2016.3.15 # get hostname hostName=`hostname` # check if hostName is null or "localhost.localdomin" ,then set it [ -z "$hostName" -o "$hostName" == "localhost.localdomin" ] && hostname "www.fredme.com" && echo "hostname is chaged to " && hostname || echo "hostname is not null or \"localhost.localdomin\"" [root@magedu ~]# hostname localhost.localdomin [root@magedu ~]# ./hostname.sh hostname is chaged to www.fredme.com [root@magedu ~]# hostname www.fredme.com [root@magedu ~]# ./hostname.sh hostname is not null or "localhost.localdomin"
腳本的執行結果狀態返回值:
如果未使用exit指定腳本的狀態返回值,那腳本的狀態返回值就是腳本中最後一條命令的執行結果返回值。
如果使用exit [n]指定(n爲0-255之間),則腳本會立即終止退出,並將n作爲腳本執行結果返回值。
注意:將exit放入()內,則只退出當前語句。
如下:
[root@magedu shell]# cat exittest1.sh #!/bin/bash [ -z $1 ] && echo "NULL" && exit 66 echo $? echo "continue" [root@magedu shell]# ./exittest1.sh ;echo $? NULL 66
# ↑↑ 正確執行,並返回狀態值
[root@magedu shell]# cat exittest2.sh #!/bin/bash [ -z $1 ] && ( echo "NULL" && exit 66 ) && echo "line1 continue" echo $? echo "continue" [root@magedu shell]# ./exittest2.sh NULL 66 continue
# ↑↑ 邏輯判斷語句後面的語句不再執行,而下一行則繼續
[root@magedu shell]# cat e.sh #!/bin/bash ( echo "nihao" exit 0 echo "hello" ) echo "ouhayi" [root@magedu shell]# ./e.sh nihao ouhayi
# ↑↑ 這個例子同上,exit語句只是跳過了exit語句後()內的語句,後面的照常執行。
位置參數變量:
在運行腳本時,在其後跟上參數,即可在腳本內以$1 ...來調用。
SCRIPTS.sh ARG1 ARG2 ...
輪轉:
shift [n]:n爲數字,將第#個位置參數變成第#-n個,小於1的參數被丟棄。
特殊變量:
$0:本腳本文件路徑,文件名
$#:腳本參數個數
$@:所有參數
$*:所有參數
實例:
#用位置參數變量爲腳本傳遞2個文本文件,並計算空白行之和。
[root@magedu tmp]# cat spaceline_SUM.sh #!/bin/bash # users give 2 files OPTION,then show the spaceline sum. # exit 2: ARGS err # exit 3: File not found or not readable # check 2 ARGS is given [ $# -gt 2 ] && echo "2 ARGS must be given.no MORE or less" && exit 2 [ $# -lt 2 ] && echo "2 ARGS must be given.no more or LESS" && exit 2 # get ARGS FILE1=$1 FILE2=$2 # check files exsit and regular file ,then get spacelines number [ -f $FILE1 -a -r $FILE1 ] && NUM1=`grep '^[[:space:]]*$' $FILE1 | wc -l` || echo "\"$FILE1\" not found or not readable" [ -f $FILE2 -a -r $FILE2 ] && NUM2=`grep '^[[:space:]]*$' $FILE2 | wc -l` || echo "\"$FILE2\" not found or not readable" # if NUM1 and NUM2 is set,show SUM,else exit [ -n "$NUM1" -a -n "$NUM2" ] && SUM=$[ $NUM1 + $NUM2 ] && echo "There is $SUM spacelines in $FILE1 and $FILE2" || exit 3 [root@magedu tmp]# mknod device b 8 0 [root@magedu tmp]# cp /etc/fstab file1 [root@magedu tmp]# cp /etc/init.d/functions file2 [root@magedu tmp]# ls -l file1 file2 device brw-r--r--. 1 root root 8, 0 Mar 15 12:04 device -rw-r--r--. 1 root root 595 Mar 15 12:05 file1 -rw-r--r--. 1 root root 13948 Mar 15 12:05 file2 [root@magedu tmp]# ./spaceline_SUM.sh file1000 2 ARGS must be given.no more or LESS [root@magedu tmp]# ./spaceline_SUM.sh file1 device "device" not found or not readable [root@magedu tmp]# ./spaceline_SUM.sh file1 file2 There is 71 spacelines in file1 and file2
選擇執行語句:
順序執行:逐條執行
選擇執行:
一個或多個分支,滿足條件則執行
單分支if語句;
if 測試條件;then
代碼分支
fi
雙分支if語句;
if 測試條件;then
代碼分支
else
代碼分支
fi
多分支if語句:
if 測試條件;then
代碼分支
elif 測試條件;then
代碼分支
...
else
代碼分支
fi
循環執行:代碼片段(循環體)執行0次或多次
後篇介紹
實例:
#參數傳遞用戶名,若用戶不存在,則添加。
[root@magedu tmp]# cat useradd.sh #!/bin/bash # get USERNAME form ARGS, if USERNAME not exist ,add it # exit 2: ARG err # # check ARG numbers [ $# -gt 1 ] && echo "You can ONLY give 1 ARG" && exit 2 [ $# -lt 1 ] && echo "1 ARG NUST given" && exit 2 # get USERNAME USERNAME=$1 # check if USERNAME not exist ,if not,add it. if id $USERNAME &> /dev/null ;then echo "\"$USERNAME\" is already exist." else useradd $USERNAME echo "$USERNAME added successful." fi [root@magedu tmp]# ./useradd.sh 1 ARG NUST given [root@magedu tmp]# ./useradd.sh fred "fred" is already exist. [root@magedu tmp]# ./useradd.sh fredme fredme added successful. [root@magedu tmp]# id fredme uid=1006(fredme) gid=1006(fredme) groups=1006(fredme)
用戶交互,錯誤排查:
read
Read a line from the standard input and split it into fields.
read [option]... [name ...]
-p prompt:輸出提示信息
-t timeout:設置超時時間
-i text:用戶未輸入,將變量賦值爲text
bash -n SCRIPTS:檢測腳本中的語法錯誤
bash -x SCRIPTS:調試執行腳本
實例:
#編寫腳本實現以下功能:
./man.sh [page] COMMAND
執行腳本,後跟一個參數,顯示此參數的man手冊
後跟2個參數,直接顯示對應章節,章節無效提示用戶。
如果有多個man手冊章節,則提示用戶輸入章節名,回車後顯示對應章節
若只有一個man手冊章節,則直接顯示此章節
[root@www tmp]# cat man.sh #!/bin/bash # 顯示man手冊,格式爲 man.sh [page] COMMAND,主要功能如下 # 若只給定COMMAND,查詢COMMAND的章節存在,則顯示章節並提示用戶輸入選擇,不存在則提示並退出; # 若給定page和comman,判斷用戶給定的page頁是否存在,存在,則直接顯示,不存在提示退出。 # 若未給定任何參數,提示退出。 # by fred PATH="/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" # 檢查參數個數,1個參數直接賦值給KEY,即當做命令關鍵字KEY;2個參數分別賦值給章節數PAGE,和命令關鍵字KEY,其他則直接提示退出腳本 if [ $# -eq 2 ];then KEY=$2 PAGE=$1 elif [ $# -eq 1 ];then KEY=$1 else echo "Usage: $0 [PAGE] COMMAND" exit 1 fi # 獲取命令man 章節號碼,並寫入到LIST屬組中。注意whatis的輸出中會存在非關鍵字的條目,相關條目也會輸出,如what is passwd會輸出sslpasswd的man章節,用grep過濾下! declare -a LIST=(`whatis -w -r "^$KEY\>" 2> /dev/null | grep "^$KEY\>" | awk '{print $2}' | tr -d '()'`) # 獲取man章節條數,並賦值給NUM變量 NUM=${#LIST[@]} # 查找命令關鍵字章節條數,若爲0,則直接提示退出腳本 if [ $NUM -eq 0 ];then echo "There is no Manual Page about \"$KEY\"." exit 16 fi # 運行到這,證明命令章節至少有1條。下面判斷用戶是否給出章節page,以及page是否有效 # 若用戶給定page,則判斷給定的page是否存在,使用YES變量標記。若存在,則繼續執行;不存在,則提示並退出腳本 YES=0 if [ $YES -eq 0 -a -n "$PAGE" ];then for P in ${LIST[@]};do [ "$P" == "$PAGE" ] && YES=1 && break done if [ $YES -ne 1 ];then echo "No manual entry for \"$KEY\" in section \"$PAGE\"." exit 2 fi fi # 運行到此處有2種情況,1.用戶給定page,並且page章節存在;2.用戶未給定page。 # 1.用戶已給定page,並且給定的page章節存在 if [ $YES -eq 1 -a -n "$PAGE" ];then man $PAGE $KEY # 針對用戶未給定page,輸出章節列表並提示用戶選擇 else echo "Manual pages found: " whatis -w $KEY | grep "^$KEY\>" read -i "${LIST[1]}" -t 5 -p "Please input page number to open: [Enter: default; n/N quit] " PAGE [ "$PAGE" == "N" -o "$PAGE" == "n" ] && exit 0 echo "man $PAGE $KEY" man $PAGE $KEY fi