shell腳本簡要總結
腳本調試
bash -n /path/to/some_script
檢測腳本中的語法錯誤
bash -x /path/to/some_script
調試執行
變量
環境變量
變量聲明、賦值:
export name=VALUE
declare -x name=VALUE
變量引用:$name, ${name}
顯示所有環境變量:
-
export
-
env
-
printenv
-
刪除:unset name
bash有許多內建的環境變量:PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1
只讀和位置變量
只讀變量:只能聲明,但不能修改和刪除
readonly name
declare -r name
位置變量:在腳本代碼中調用通過命令行傳遞給腳本的參數
$1, $2, …:對應第1、第2等參數,shift [n]換位置
$0: 命令本身
$*: 傳遞給腳本的所有參數,全部參數合為一個字符串
$@: 傳遞給腳本的所有參數,每個參數為獨立字符串
$#: 傳遞給腳本的參數的個數
$@ $* 只在被雙引號包起來的時候才會有差異
示例:判斷給出的文件的行數 linecount="$(wc -l $1| cut -d' ' -f1)" echo "$1 has $linecount lines."
變量的運算
bash中的算術運算:help let
+, -, *, /, %取模(取余), **(乘方)
增強型賦值:
+=, -=, *=, /=, %=,++,–
實現算術運算:
(1) let var=算術表達式
(2) var=$[算術表達式]
(3) var=$((算術表達式))
(4) var=$(expr arg1 arg2 arg3 …)
(5) declare –i var = 數值
(6) echo "算術表達式" | bc
bash有內建的隨機數生成器:$RANDOM(1-32767)
echo $[$RANDOM%50] :0-49之間隨機數
注意:
乘法符號有些場景中需要轉義,如 *
邏輯運算 && || !
短路運算:
短路與:
第一個為0,結果必定為0;
第一個為1,第二個必須要參與運算;
短路或:
第一個為1,結果必定為1;
第一個為0,第二個必須要參與運算;
退出狀態
進程使用退出狀態來報告成功或失敗
? 0 代表成功,1-255代表失敗
? $? 變量保存最近的命令退出狀態
例如:
$ ping -c1 -W1 hostdown &> /dev/null
$ echo $?
bash自定義退出狀態碼
exit [n]:自定義退出狀態碼;
注意:腳本中一旦遇到exit命令,腳本會立即終止;終止退出狀態取決于exit命令后面的數字
注意:如果未給腳本指定退出狀態碼,整個腳本的退出狀態碼取決于腳本中執行的最后一條命令的狀態碼
條件測試
判斷某需求是否滿足,需要由測試機制來實現;專用的測試表達式需要由測試命令輔助完成測試過程;
評估布爾聲明,以便用在條件性執行中
-
若真,則返回0
-
若假,則返回1
測試命令:
-
test EXPRESSION
-
[ EXPRESSION ]
-
[[ EXPRESSION ]]
注意:EXPRESSION前后必須有空白字符test命令
長格式的例子:
$ test "$A" == "$B" && echo "Strings are equal"
$ test “$A” -eq “$B” \
&& echo "Integers are equal"
簡寫格式的例子:
$ [ "$A" == "$B" ] && echo "Strings are equal"
$ [ "$A" -eq "$B" ] && echo "Integers are equal"
bash的測試類型
數值測試:
-
-gt: 是否大于
-
-ge: 是否大于等于
-
-eq: 是否等于
-
-ne: 是否不等于
-
-lt: 是否小于
-
-le: 是否小于等于
字符串測試:
-
==:是否等于;
-
>: ascii碼是否大于ascii碼
-
<: 是否小于
-
!=: 是否不等于
-
=~: 左側字符串是否能夠被右側的PATTERN所匹配
注意: 此表達式一般用于[[ ]]中;擴展的正則表達式 -
-z "STRING":字符串是否為空,空為真,不空為假
-
-n "STRING":字符串是否不空,不空為真,空為假
注意:用于字符串比較時的用到的操作數都應該使用引號
文件測試
存在性測試
-
-a FILE:同-e
-
-e FILE: 文件存在性測試,存在為真,否則為假
存在性及類別測試
-
-b FILE:是否存在且為塊設備文件
-
-c FILE:是否存在且為字符設備文件
-
-d FILE:是否存在且為目錄文件
-
-f FILE:是否存在且為普通文件
-
-h FILE 或 -L FILE:存在且為符號鏈接文件
-
-p FILE:是否存在且為命名管道文件
-
-S FILE:是否存在且為套接字文件
文件測試
文件權限測試:
-
-r FILE:是否存在且可讀
-
-w FILE: 是否存在且可寫
-
-x FILE: 是否存在且可執行
文件特殊權限測試:
-
-u FILE:是否存在且擁有suid權限
-
-g FILE:是否存在且擁有sgid權限
-
-k FILE:是否存在且擁有sticky權限
文件測試
文件大小測試:
-
-s FILE: 是否存在且非空
文件是否打開:
-
-t fd: fd表示文件描述符是否已經打開且與某終端相關
-
-N FILE:文件自動上一次被讀取之后是否被修改過
-
-O FILE:當前有效用戶是否為文件屬主
-
-G FILE:當前有效用戶是否為文件屬組
雙目測試:
FILE1 -ef FILE2: FILE1與FILE2是否指向同一個設備上的相同inode
FILE1 -nt FILE2: FILE1是否新于FILE2
FILE1 -ot FILE2: FILE1是否舊于FILE2
組合測試條件
第一種方式:
COMMAND1 && COMMAND2 并且
COMMAND1 || COMMAND2 或者 ! COMMAND 非
如:[[ -r FILE ]] && [[ -w FILE ]]
第二種方式:
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
! EXPRESSION
必須使用測試命令進行;
示例: # [ -z “$HOSTNAME” -o $HOSTNAME "==\ "localhost.localdomain" ] && hostname www.magedu.com # [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
使用read命令來接受輸入
使用read來把輸入值分配給一個或多個shell變量:
-
-p 指定要顯示的提示
-
-t 指定讀取值時等待的時間(秒)。
read 從標準輸入中讀取值,給每個單詞分配一個變量所有剩余單詞都被分配給最后一個變量
read -p “Enter a filename: “ FILE
其他
read -n 2 var
從輸入中讀取兩個字符并存入變量var,不需要按回車讀取。read -r line
允許輸入包含反斜杠read -d ":" var
用定界符“:”結束輸入行。
實例
從標準輸入讀取輸入并賦值給變量name。
#read name ------ #等待讀取輸入,直到回車后表示輸入完畢,并將輸入賦值給變量
HelloWorld ?。?#控制臺輸入Hello#echo $name -------#打印變量
HelloWorld等待一組輸入,每個單詞之間使用空格隔開,直到回車結束,并分別將單詞依次賦值給這三個讀入變量。
#read one two three
1 2 3 ?。?在控制臺輸入1 2 3,它們之間用空格隔開。#echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3
REPLY示例
#read #等待控制臺輸入,并將結果賦值給特定內置變量REPLY。
This is REPLY #在控制臺輸入該行。#echo $REPLY #打印輸出特定內置變量REPLY,以確認是否被正確賦值。
This is REPLY
-p 選項示例
#read -p "Enter your name: " ?。? #輸出文本提示,同時等待輸入,并將結果賦值給REPLY。
Enter you name: stephen ?。?在提示文本之后輸入stephen#echo $REPLY
stephen
流程控制各個命令的簡要語法格式
條件選擇if語句
選擇執行:
注意:if語句可嵌套單分支
if 判斷條件;then
條件為真的分支代碼
fi
雙分支
if 判斷條件; then
條件為真的分支代碼
else
條件為假的分支代碼
fi
多分支
if 判斷條件1; then
if-true
elif 判斷條件2; then
if -ture
elif 判斷條件3; then
if -ture
…
else
all-false
fi
根據命令的退出狀態來執行命令 if ping -c1 -W2 station1 &> /dev/null; then echo 'Station1 is UP' elif grep "station1" ~/maintenance.txt &> /dev/null then echo 'Station1 is undergoing maintenance‘ else echo 'Station1 is unexpectedly DOWN!' exit 1 fi
寫一個腳本 /root/bin/filetype.sh, 判斷用戶輸入文件路徑,顯示其文件類型(普通,目錄,鏈接,其它文件類型)
第一種方法: if [[ $# -lt 1 ]] then echo -e "Error: No argument.\n\tUsage: $0 FILENAME" exit 1 else if [[ -e $1 ]] then FileType=`ls -ld $1 | cut -c1` case $FileType in d) echo "$1 is a diretory" ;; -) echo "$1 is a common file" ;; l) echo "$1 is a link file" ;; *) echo "$1 is other file" esac else echo "$1: no such file or diretory." fi fi unset FileType
第二種方法
if [[ $# -lt 1 ]] then echo -e "Error: No argument.\n\tUsage: $0 FILENAME" exit 1 else if [[ ! -e $1 ]] then echo "$1: No such file or diretory" elif [[ -d $1 ]] then echo "$1 is a diretory" elif [[ -L $1 ]] then echo "$1 is a link file" elif [[ -f $1 ]] then echo "$1 is a common file" fi fi
條件判斷:case語句
語法
case 變量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
…
*)
默認分支
;;
esac
例
1、編寫腳本/root/bin/createuser.sh,實現如下功能:使
用一個用戶名做為參數,如果指定參數的用戶存在,就顯示
其存在,否則添加之;顯示添加的用戶的id號等信息#!/bin/bash # read -p "請輸入一個ID :" Id id $Id &/dev/null if [ $? -eq 0 ];then echo "用戶$Id已存在" else useradd $Id echo "$Id用戶已創建" id $Id fi
for循環
格式 1
for 變量名 in 列表;do
循環體
done執行機制:
依次將列表中的元素賦值給“變量名”; 每次賦值后即執
行一次循環體; 直到列表中的元素耗盡,循環結束列表可以由命令
命令
or $(命令) 生成或者指定
格式2 (c語言格式)for (( i=1 ; i<=9 ; i++ ))
for ((控制變量初始化;條件判斷表達式;控制變量的修正表達式))
do
循環體
done控制變量初始化:僅在運行到循環代碼段時執行一次
控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而后再做條件判斷注意:(())雙括號
(( ... )): (( 表達式 )) 估值算術表達式。 表達式按照算術法則進行估值。 等價于 "let 表達式". 退出狀態 如果表達式估值為0則返回 1;否則返回0。
例:
# 99乘法表 for i in `seq 9` do for j in `seq 1 $i` do echo -ne "$i*$j=$(($i*$j))\t" done echo done echo "==================== plan 2 ====================" for (( i=1 ; i<=9 ; i++ )) do for (( j=1 ; j<=i ; j++ )) do echo -ne "$i*$j=$(($i*$j))\t" done echo done unset i unset j
while循環
語法:
while CONDITION; do
循環體
done
CONDITION:循環控制條件;進入循環之前,先做一次判斷;每一次循環之后會再次做判斷;條件為“true”,則執行一次循環;直到條件測試狀態為“false”終止循環
因此:CONDTION一般應該有循環控制變量;而此變量的值會在循環體不斷地被修正
進入條件:CONDITION為true; 若想死循環 while true
退出條件:CONDITION為false
例
1.編寫腳本,利用變量RANDOM生成10個隨機數字,輸出
這個10數字,并顯示其中的最大者和最小者
#!/bin/bash #qzx # i=10 a=$RANDOM max=$a min=$a while [ $i -ge 1 ] do [ $max -lt $a ] && max=$a [ $min -gt $a ] && min=$a echo "$a" a=$RANDOM let i-- done echo "最大值$max" echo "最小值$min"
2.* 金字塔
* *** ***** ******* #!/bin/bash #qzx # read -p "請輸入要打印的行數: " j let xing=2*j-1 for i in `seq 1 2 $xing` do let j-- let a=j while [ $a -gt 0 ] do echo -n " " let a-- done for b in `seq 1 $i` do echo -n "*" sleep 0.1 done echo -ne "\n" done
until循環
語法 1
until CONDITION; do
循環體
done進入條件: CONDITION 為 false
退出條件: CONDITION 為 true
語法2
while循環的特殊用法(遍歷文件的每一行):
while read line; do
循環體
done < /PATH/FROM/SOMEFILE
依次讀取/PATH/FROM/SOMEFILE文件中的每一行,且將
行賦值給變量line
語法1
#!/bin/bash #打印國際象棋棋盤 #qzx read -p "NxN:please input N " N let i=$N let j=$N until [ $i -le 0 ] do let j=N until [ $j -le 0 ] do let a=i+j let a=a%2 if [ $a -eq 0 ] then echo -ne "\033[41m \033[0m" else echo -ne "\033[42m \033[0m" fi let j=j-1 done echo -ne "\n" let i=i-1 done
語法2
#!/bin/bash # #qzx while read gai do name=$(echo "$gai" | cut -d: -f1) numb=$(echo "$gai" | cut -d: -f5) if [[ -z "$numb" ]] then chfn -f $name $name &>/dev/null chfn -p "1234567" $name &>/dev/null echo ""$name"添加成功" fi done < ~/bin/passwd
循環控制語句
continue
用于循環體中(do —–continue—- done)
continue [N]:提前結束第N層的本輪循環,而直接進入下一輪判斷;最內層為第1層。默認為1
break
用于循環體中(do —-break—-done)
break [N]:提前結束第N層循環,最內層為第1層。默認為1
例、每隔3秒鐘到系統上獲取已經登錄的用戶的信息;如果發現用戶hacker登錄,則將登錄時間和主機記錄于日志/var/log/login.log中,并提示該用戶退出系統。
#/bin/bash until who |grep -q "^hacker\b" ;do sleep 3 done who | grep "^hacker"|tr -s ' '|cut -d' ' -f3,5 >> /var/log/login.log echo "you should logout system" | mail hacker echo "reminded and login record in /var/log/login.log"
select 循環與菜單
語法:
select variable in list
do
循環體命令
doneselect 循環主要用于創建菜單,按數字順序排列的菜單項將顯示在標準錯誤上,并顯示 PS3 提示符,等待用戶輸入
用戶輸入菜單列表中的某個數字,執行相應的命令
用戶輸入被保存在內置變量 REPLY 中。
select 是個無限循環,因此要記住用 break 命令退出循環,或用 exit 命令終止腳本。也可以按 ctrl+c 退出循環。
select 經常和 case 聯合使用
與 for 循環類似,可以省略 in list ,此時使用位置參量
函數
函數由兩部分組成:函數名和函數體。
語法一:
function f_name {
…函數體…
}語法二:
function f_name () {
…函數體…
}語法三:
f_name (){
…函數體…
}
函數使用
函數的定義和使用:
-
可在交互式環境下定義函數
-
可將函數放在腳本文件中作為它的一部分
-
可放在只包含函數的單獨文件中
調用:函數只有被調用才會執行;
調用:給定函數名
函數名出現的地方,會被自動替換為函數代碼
函數的生命周期:被調用時創建,返回時終止
函數返回值
函數有兩種返回值:
函數的執行結果返回值:
(1) 使用echo或printf命令進行輸出
(2) 函數體中調用命令的輸出結果
函數的退出狀態碼:
(1) 默認取決于函數中執行的最后一條命令的退出狀態碼
(2) 自定義退出狀態碼,其格式為:
-
return 從函數中返回,用最后狀態命令決定返回值
-
return 0 無錯誤返回。
-
return 1-255 有錯誤返回
使用函數文件和創建函數
使用函數:可以將經常使用的函數存入函數文件,然后將函數文件載入shell。
-
文件名可任意選取,但最好與相關任務有某種聯系。例如:functions.main
-
一旦函數文件載入shell,就可以在命令行或腳本中調用函數。可以使用set命令查看所有定義的函數,其輸出列表包括已經載入shell的所有函數。
-
若要改動函數,首先用unset命令從shell中刪除函數。改動完畢后,再重新載入此文件。、
創建函數文件
函數文件示例:
$cat functions.main #!/bin/bash #functions.main findit() { if [ $# -lt 1 ] ; then echo "Usage:findit file" return 1 fi find / -name $1 –print }
載入函數
意義: 函數文件已創建好后,要將它載入shell
定位函數文件并載入shell的格式:
. filename 或 source filename
注意:此即<點> <空格> <文件名>
這里的文件名要帶正確路徑
示例:上例中的函數,可使用如下命令: $ . functions.main
檢查載入函數 set命令
使用set命令檢查函數是否已載入。set命令將在shell中顯示
所有的載入函數。
刪除shell函數
現在對函數做一些改動。首先刪除函數,使其對shell不可用。使用unset命令完成此功能.
命令格式為:
unset function_name
實例: $unset findit
再鍵入set命令,函數將不再顯示
函數變量
變量作用域:
-
環境變量:當前shell和子shell有效
-
本地變量:只在當前shell進程有效,為執行腳本會啟動
-
專用子shell進程;因此,本地變量的作用范圍是當前shell腳本程序文件,包括腳本中的函數。
-
局部變量:函數的生命周期;函數結束時變量被自動銷毀
注意:如果函數中有局部變量,如果其名稱同本地變量,使
用局部變量。
在函數中定義局部變量的方法
local NAME=VALUE
函數的遞歸
-
函數直接或間接調用自身
-
注意遞歸層數
例:計算一個正整數的階乘
#!/bin/bash #:0!=1,n!=(n-1)!×n fact() { if [ $1 -eq 0 -o $1 -eq 1 ] ; then echo 1 else echo "$[$1*$(fact $[$1-1])]" fi } fact $1
例:波納契數列以如下被以遞歸的方法定義:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)寫一個函數,求n階斐波那契數列
#/bin/bash #qzx # fun(){ if [ $1 -eq 0 ] then echo "0" elif [ $1 -eq 1 ] then echo "1" else echo "$[$(fun $[$1-2])+$(fun $[$1-1])]" fi } fun $1
綜合性練習
1、編寫服務腳本/root/bin/testsrv.sh,完成如下要求
(1) 腳本可接受參數:start, stop, restart, status
(2) 如果參數非此四者之一,提示使用格式后報錯退出
(3) 如是start:則創建/var/lock/subsys/SCRIPT_NAME, 并顯示“啟動成功”
考慮:如果事先已經啟動過一次,該如何處理?
(4) 如是stop:則刪除/var/lock/subsys/SCRIPT_NAME, 并顯示“停止完成”考慮:如果事先已然停止過了,該如何處理?
(5) 如是restart,則先stop, 再start考慮:如果本來沒有start,如何處理?
(6) 如是status, 則如果/var/lock/subsys/SCRIPT_NAME文件存在,則顯示“SCRIPT_NAME is running…”
如果/var/lock/subsys/SCRIPT_NAME文件不存在,則顯示“SCRIPT_NAME
is stopped…”
其中:SCRIPT_NAME為當前腳本名
(7)在所有模式下禁止啟動該服務,可用chkconfig 和 service命令管理#/bin/bash #qzx # fun (){ if [[ -f /var/lock/subsys/SCRIPT_NAME ]] then a=1 else a=0 fi } fun1(){ fun if [ $a -eq 1 ] then echo "已經啟動無需重復操作" else touch /var/lock/subsys/SCRIPT_NAME echo "啟動成功" fi } fun2(){ fun if [ $a -eq 0 ] then echo "已經停止成功,無需重復操作" else rm -rf /var/lock/subsys/SCRIPT_NAME echo "停止成功" fi } fun3(){ fun [[ $a -eq 1 ]] && echo ""$0" is running..." || echo ""$0" is stopped..." } PS3="請輸入 start stop restart status 所對應的數字 " select str in start stop restart status do case $str in start) fun1 ;; stop) fun2 ;; restart) fun1;fun2 ;; status) fun3 ;; *) echo "您輸入的不正確。。" break ;; esac done2、編寫腳本/root/bin/copycmd.sh
(1) 提示用戶輸入一個可執行命令名稱;
(2) 獲取此命令所依賴到的所有庫文件列表
(3) 復制命令至某目標目錄(例如/mnt/sysroot)下的對應路徑下;
如:/bin/bash ==/mnt/sysroot/bin/bash
/usr/bin/passwd ==/mnt/sysroot/usr/bin/passwd
(4) 復制此命令依賴到的所有庫文件至目標目錄下的對應路徑下:
如:/lib64/ld-linux-x86-64.so.2 ==/mnt/sysroot/lib64/ldlinux-x86-64.so.2
(5)每次復制完成一個命令后,不要退出,而是提示用戶鍵入新的要復制的命
令,并重復完成上述功能;直到用戶輸入quit退出#/bin/bash #qzx # funku(){ for meihangdu in `ldd $quanming | sed -r -n 's@.*(/.*) \(.*$@\1@p'` do local lujing=`dirname $meihangdu` mkdir -p /mnt/sysroot/$lujing &>/dev/null cp -p $meihangdu /mnt/sysroot/$lujing &>/dev/null done } while true do read -p "請輸入一個可執行的命令名稱 " cmds which $cmds if [ $? -eq 0 ] then quanming=`which $cmds | tail -1` lujing=`dirname $quanming` mkdir -p /mnt/sysroot/$lujing &>/dev/null cp -p $quanming /mnt/sysroot/$lujing &>/dev/null funku elif [[ $cmds == "quit" ]] then break else echo "您輸入的命令不對" fi done
原創文章,作者:qzx,如若轉載,請注明出處:http://www.www58058.com/38543