第14章,Shell腳本編程進階
Linux學習從入門到打死也不放棄,完全筆記整理(持續更新,求收藏,求點贊~~~~)
http://blog.51cto.com/13683480/2095439
第14章,Shell腳本編程進階
本章內容:
條件判斷
循環
信號捕捉
函數
數組
高級字符串操作
高級變量
Expect
過程式編程語言執行方式:
順序執行,選擇執行,循環執行
條件選擇-----------------------------------------------------------------------
if語句:
結構: 可嵌套
單分支:
if 判斷條件;then
條件判斷為真的執行代碼
fi
雙分支:
if 判斷條件;then
條件判斷為真的執行代碼
(此段若想為空,使用":")
else
條件判斷為假的執行代碼
fi
多分支:
if 條件判斷1;then
cmd..
elif 條件判斷2;then
cmd..
elif 條件判斷3;then
cmd..
else
以上條件全為假的分支代碼
fi
case語句:
格式:
case 變量引用 in
pat1)
分支1
;;
pat2)
分支2
;;
...
*)
默認分支
;;
esac
註意: case支持glob風格的通配符
* 任意長度任意字符
? 任意單個字符
[abc] 指定範圍內的任意單個字符
a|b a或b
循環執行------------------------------------------------------------------------
將某代碼段重復多次運行
重復運行多少次
循環次數事先已知
循環次數事先未知
有進入條件和退出條件
for,while,until
for循環
格式:
for 變量名 in 列表;do
循環體代碼
done
機制:
依次將列表中的元素賦值給"變量名"
每次賦值後即執行一次循環體,直到列表中的元素耗盡,循環結束
列表生成方式:
1 直接給出列表
2 整數列表
{1..10..2}
$(seq 1 2 10)
3 返回列表的命令
$(cmd)
4 使用glob,如:*.sh
5 變量引用
$@,$*
while循環
格式:
while CONDITION;do
循環體
done
CONDITION :
循環控制條件
進入循環之前,先做一次判斷
每一次循環之後會再次做判斷
條件為true,則執行一次循環,直到條件測試狀態為false 終止循環
因此: CONDITION一般應該有循環控制變量,而此變量的值會在循環體不斷的被修正
進入條件:CONDITION為true
退出條件:CONDITION為false
until循環
格式:
until CONDITION;do
循環體
done
進入條件:CONDITION 為 false
退出條件:CONDITION 為 true
循環控制語句:
用於循環體中
continue [N] 默認為1
用於循環體中,提前結束第N層的本輪循環,而直接進入下一輪判斷
所在層為第1層
break [N] 默認為1
用於循環體中,提前結束第N層循環,所在層為第1層
shift [N] 默認為1
用於將參數列表list 左移指定的次數,默認1次
參數列表list 一旦被移動,最左端的那個參數就從列表中刪除。
while循環遍歷位置參數列表時,常用到shift
創建無限循環:
1 while true;do
循環體代碼
done
2 until false;do
循環體
done
特殊用法:
while 循環的特殊用法(遍歷文件的每一行)
while read 變量名;do
循環體代碼
done < /file
一次讀取file 文件中的每一行,且將行賦值給變量
PS: 也可以使用 cmd |while read;do
循環體
done
但是循環中的數組值貌似不能輸出,即如在done之後echo 數組中的值為空 ,需要註意
雙小括號方法,即((....))格式,也可以用於算術運算
也可以是bash實現C語言風格的變量操作
i=10
((i++))
for 循環 的特殊格式:
for((控制變量初始化;條件判斷表達式;控制標量的修正表達式));do
循環體代碼
done
如: for ((i=1;i<=10;i++));do
echo $i
done
控制變量初始化:
僅在運行到循環代碼段時執行一次
控制變量的修正表達式:
條件判斷表達式:進入循環先做一次條件判斷,true則進入循環
每輪循環結束會先進行控制變量的修正運算,而後在做條件判斷
select循環與菜單:
格式:
select 變量 in 列表;do
循環體命令
done
select 循環主要用於創建菜單,按數字順序排列的菜單項將顯示在標準錯誤上,並顯示
PS3提示符,等待用戶輸入
用戶輸入菜單列表上的某個數字,執行相應的命令
用戶輸入被保存在內置變量REPLY中
PS2: 多行重定向的提示符
PS3: select的提示符
REPLY 保存select的用戶輸入
select與case
select是個無限訓話,因此要記住用break命令退出循環,或用exit命令終止腳本。
也可以按Ctrl+c退出循環
select 經常和case聯合使用
與for循環類似,可以省略in list ,此時使用位置參量
trap 信號捕捉:
trap ‘觸發指令’信號
自定義進程收到系統發出的指定信號後,將執行觸發指令,而不會執行原操作
trap ''信號
忽略信號的操作
trap '-' 信號
恢復原信號的操作
trap -p
列出自定義信號操作
註意事項:
信號: 格式可以是 int INT sigint SIGINT 2
信號捕捉之後,雖然不會立刻結束腳本,但是腳本當前運行的命令卻會被終止
如: trap 'echo trap a sig2' 2
sleep 10000
ping 192.168.0.1
如果捕捉到2信號,不會退出腳本,但是sleep會被打斷,而繼續執行ping命令
函數:------------------------------------------------------------------------------
函數介紹:
函數function是由若幹條shell命令組成的語句塊,實現代碼重用和模塊化編程
它與shell程序形式上是相似的,不同的是它不是一個單獨的進程,不能獨立運行,
而是shell程序的一部分
函數和shell程序區別在於:
shell程序在子shell中運行
而shell函數在當前shell中運行。因此在當前shell中,函數可以對shell變量
進行修改
定義函數:
函數由兩部分組成:函數名和函數體
help function
語法1:
f_name(){
函數體
}
語法2:
function f_name()
{
函數體
}
語法3:
function f_name{
函數體
}
可以交互式環境下定義函數,如:
[root@centos6 ~/bin]$func_name1(){
> echo nihao
> echo wohenhao
> ping -c1 -w1 192.168.0.1
> }
[root@centos6 ~/bin]$func_name1
nihao
wohenhao
connect: Network is unreachable
可將函數放在腳本文件中作為它的一部分
可放在只包含函數的單獨文件中
函數調用:
函數只有被調用才會執行
函數名出現的地方,會被自動替換為函數代碼
函數的生命周期:
被調用時創建,返回時終止
在腳本中用戶前必須定義,因此應該將函數定義放在腳本開始部分,直至shell首次
發現它之後才能使用
調用函數僅使用其函數名即可
函數返回值:
函數由兩種返回值:
函數的執行結果返回值:
1 使用echo等命令進行輸出
2 函數體中調用命令的輸出結果
函數的退出狀態碼:
1 默認取決於函數中執行的最後一條命令的退出狀態碼
2 自定義退出狀態碼,return命令
return : 從函數中返回,用最後狀態命令決定返回值
return 0 無錯誤返回
return 1-255 有錯誤返回
刪除函數:
unset func_name1
函數文件:
創建函數文件:
#!/bin/bash
# function.main
f_hello()
{
echo helio
}
f_func2(){
...
}
...
系統自帶函數文件:/etc/rc.d/init.d/functions
使用函數文件:
可以將進程使用的函數存入函數文件,然後將函數文件載入shell
文件名可任意選取,但最好與相關任務有某種聯系,如 function.main
一般函數文件載入shell,就可以在命令行或腳本中調用函數,
可以使用set命令查看所有定義的函數,其輸出列表包括已經載入shell的所有函數
若要改動函數,首先用unset命令從shell中刪除函數,改動完畢後,再重新載入此文件
載入函數:
要想使用已創建好的函數文件,要將他載入shell
使用
. /path/filename
source /path/filename
如果使用source 載入函數之後,對函數文件的某個函數做了修改,需要unset函數之後重新載入
unset func_name1
或者exit 重新登錄然後再次source
默認本shell進程有效,如需函數子進程有效,需聲明
export -f func_name
declare -xf
如需查看當前所有的全局函數
export -f 或 declare -xf
例如:
[root@centos6 ~/bin]$export -f func_release
[root@centos6 ~/bin]$export -f
func_release ()
{
declare rel=$(cat /etc/centos-release|tr -dc [0-9]|cut -c1);
echo $rel
}
declare -fx func_release
[root@centos6 ~/bin]$
函數參數:
函數可以接受參數:
調用函數時,在函數名後面以空白分隔給定參數列表即可;
例如 func_name arg1 arg2 ...
在函數體當中,可以使用$1,$2..調用這些參數;還可以使用$@,$*,$# 等特殊變量
註意區分腳本的位置參數和傳遞給函數的位置參數
函數變量:
變量作用域:
環境變量: 當前shell和子shell有效
本地變量: 只在當前shell進程有效,為執行腳本會啟動專用shell進程;
因此,本地變量的作用範圍是當前shell腳本程序文件,包括腳本中的函數
局部變量: 函數的生命周期;函數結束時變量會被自動銷毀
註意: 如函數中的變量名同本地變量,使用局部變量
函數中定義局部變量:
local name=
declare name= declare自帶局部變量屬性
declare -ig 在函數中定義普通變量,centos6不支持
函數遞歸:
函數直接或間接調用自身
註意遞歸層數
函數遞歸示例:
階乘是基斯頓·卡曼於 1808 年發明的運算符號,是數學術語
一個正整數的階乘(factorial)是所有小於及等於該數的正整數的積,並且有0的
階乘為1,自然數n的階乘寫作n!
n!=1×2×3×...×n
階乘亦可以遞歸方式定義:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!
示例:
fact.sh
#!/bin/bash
func_factorial()
{
if [ $1 = 0 ];then
echo 1
else
echo $[$1*`func_factorial $[$1-1]`]
fi
}
func_factorial $1
fork×××:
fork×××是一種惡意程序,它的內部是一個不斷fork進程的無限循環,實質是一個簡單
的遞歸程序。由於程序是遞歸的,如果沒有任何顯示,這會導致整個簡單的程序迅速
耗盡系統所有資源
函數實現:
:(){ :|:&};:
bomb(){ bomb|bomb&};bomb
腳本實現:
cat bomb.sh
#!/bin/bash
./$0|./$0&
多種語言版本
數組---------------------------------------------------------------------------------
變量: 存儲單個元素的內存空間
數組: 存儲多個元素的連續的內存空間,相當於多個變量的集合
數組名和索引:
索引:編號從0開始,屬於數值索引
bash4.0版本之後,索引可以支持使用自定義的格式,而不僅是數值格式,即為關聯索引
bash中的數組支持稀疏格式(索引不連續)
聲明數組:
declare -a array_name
declare -A ARRAY_NAME 關聯索引
彼此不可互相轉化
數組賦值:
數組元素的賦值:
1 一次只賦值一個元素
array_name[index]=VALUE
如:
weekdays[0]="sunday"
weekdays[4]="thursday"
2 一次賦值全部元素
array_name=("VAL1" "val2" "val3"...)
3 只賦值特定元素
array_name=([0]=varl [3]=val2...)
4 交互式數組值對賦值
read -a array_name1
如:
[root@centos7 ~]$read -a array_name1
monday tusday wensday thursday
[root@centos7 ~]$echo ${array_name1[@]}
monday tusday wensday thursday
註意:
如果先賦值單個元素array[0]=a,
再使用賦值全部 array=(b c d) 或者特定賦值 array=([1]=e [2]=f [3]=g)
會使之前單個元素array[0]被覆蓋消失
索引數組可以無需聲明直接賦值使用
關聯數組必須先聲明之後才能賦值使用
如:[root@centos7 ~]$array3[0]=mage
[root@centos7 ~]$array3[1]=zhangsir
[root@centos7 ~]$echo ${array3[*]}
mage zhangsir
[root@centos7 ~]$echo ${array3[1]}
zhangsir
[root@centos7 ~]$echo ${array3[0]}
mage
[root@centos7 ~]$array4[ceo]=mage
[root@centos7 ~]$array4[cto]=zhangsir
[root@centos7 ~]$echo ${array4[*]}
zhangsir
直接賦值使用關聯數組會賦值失敗,只顯示最後一個值
數組引用:
引用數組元素:
${array_name[index]}
註意:省略[index表示引用下標為0的元素]
引用數組所有元素
${array_name[@]}
${array_name[*]}
數組的長度(數組中元素的個數)
${#array_name[*]}
${#array_name[@]}
刪除數組中的某元素:導致稀疏格式
unset array[index]
刪除整個數組
unset array
數組數據處理
引用數組中的元素:
數組切片:${array[@]:offset:number}
offset: 要跳過的元素個數
number:要取出的元素個數
${array[0]:offset} 取偏移量之後的所有元素
${array[0]: -n} 取最後n個元素
${array[0]::2} 取開頭2個元素
${array[0]: -m:-n} 跳過最後第n+1個到第m個元素間的所有元素
向數組中追加元素:
array[${#array[*]}]=
關聯數組:
declare -A array_name
array_name=([idx_name1]=val1 [idx_name2]=val2 ...)
字符串處理-------------------------------------------------------------------------
字符串切片:
${#var}: 返回字符串變量var的長度
${var:offset}: 返回字符串變量var中從第off個字符後(不包括第offset個字符)的字符
開始,到最後的部分,offer的取值在0到${#var}-1之間(bash4.2之後允許為負值)
${var:offset:number}: 返回字符串變量var中第off個之後的num個字符(不包含off)
${var: -n}: 取字符串最右側那個字符(冒號後需加一個空格)
${var: -n:-m}: 取倒數第m+1個字符 到 倒數第n個字符
基於模式取子串:
${var#*word} 其中var為變量名,不需要加$引用,word可以是指定的任意字符串
功能:自左而右,查找var變量所存儲的字符串中,第一次出現的word,刪除字符串開頭
至第一次出現word字符之間的所有字符
${var##*word}
貪婪模式,刪除字符串開頭到最後一次”word“指定的字符之間的所有內容
${var%word*} 其中word可以是指定的任意字符串
功能: 自右邊而左,查找var變量所存儲的字符串中,第一次出現word,刪除字符串最後一個字符
向左至第一次出現word字符之間的所有字符
${var%%word*}
自右而左,刪除至最後一個word所指定的字符串
查找替換:
${var/pattern/substr}:
查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替換之
${var//pattern/substr}:
替換所有能被pattern所匹配到的字符串,以substr替換
${var/#pattern/substr}
行首被pattern匹配,並替換
${var/%pattern/substr}:
行尾被pattern匹配,並替換
查找刪除:
${var/pattern}: 刪除第一次被pattern匹配到的字符串
${var//pattern}: 刪除所有被pattern匹配到的字符串
${var/#pattern}: 刪除pattern為行首所匹配到的字符串
${var/%pattern}: 刪除pattern為行尾所匹配到的字符串
字符串大小寫轉換:
${var^^} 把var中所有的小寫字母轉換為大寫
${var,,} 把var中所有的大寫字母轉換為小寫
高級變量用法:-------------------------------------------------------------------
變量賦值:
var=${str-expr} str為變量,expr為字符串
如果str沒有沒配置,var=expr
如果str配置且為空,var=
如果str配置且非空,var=$str
var=${str:-expr}
如果str沒有配置或者為空,var=expr
如果str已配置且非空: var=$str
其他諸多用法,此處不一一列舉,如有需要查看相關表格查詢
高級變量用法:有類型變量
shell變量一般是無類型的,但是bash shell提供了declare和同樣typeset兩個命令
用於指定變量的類型,兩個命令是等價的
declare:
declare [option] [var]
-r 聲明或顯示只讀變量
-i 整型數
-a 數組
-A 關聯數組
-f 顯示已定義的所有函數名及其內容
-F 僅顯示已定義的所有函數名
-x 聲明或顯示環境變量和函數
-xf 全局函數
-l 聲明變量為小寫字母
-u 聲明變量為大寫字母
declare -ig 在函數中定義普通變量,centos6不支持
eval:
eval命令將會首先掃描命令進行所有的置換,然後再執行該命令。該命令適用於那些一次
掃描無法實現其功能的變量:該命令對變量進行兩次掃描
示例: eval echo {1..$i}
間接變量引用:
如果第一個變量的值是第二個變量的名字,從第一個變量引用第二個變量的值
就稱為間接變量引用
如:
var1=var2
var2=nnn
bash 提供了兩種格式實現間接變量引用
eval var=\$$var1
var3=${!var1}
如:
var1=var2
var2=nnn
var3=${!var1} 或者 eval var3=\$$var1
echo $var3
nnn
創建臨時文件:---------------------------------------------------------------------
mktemp :
創建並顯示臨時文件,可避免沖突
mktemp [option]..[template]
template: filenameXXX
-d 創建臨時目錄
-p DIR 或 --tmpdir=DIR 指明臨時文件所存放目錄位置
示例:
tmpfile1=`mktemp httptmp-XXX`
tmpdir=`mktemp -d /tmp/roottmp-XXXX`
tmpdir=`mktemp --tmpdir=/tmp roottmp-XXXX`
安裝復制文件:
install 命令:
install [options] source dest 單文件
install [] source dir 單文件
install [] -t dir source 單文件
install [] -d dir 創建空目錄
選項:
-m mode 默認755
-o owner
-g group
示例:
expect介紹-----------------------------------------------------------------------
expect 是由Don Libes 基於Tcl(Tool Command Language)語言開發的
主要應用於自動化交互式操作的場景,借助expect處理交互的命令,可以將交互過程
如:ssh登錄,ftp登錄等寫在一個腳本上,使之自動化完成。尤其適用於需要對多臺
服務器執行相同操作的環境中,可以大大提供系統管理人員的工作效率
expect命令:
expect [選項] [-c cmds] [[ -f[f|b]] cmdfile ] [args]
選項:
-c 從命令行執行expect腳本,默認expect是交互地執行的
示例:expect -c 'expect "hello" { send "you said hello\n" }'
-d 可以輸出調試信息
示例:expect -d ssh.exp
expect中的相關命令
spawn 啟動新的進程
send 用戶向進程發送字符串
expect 從進程接受字符串
interact 允許用戶交互
exp_continue 匹配多個字符串,在執行動作後加此命令
expect最常用的語法(tcl語言:模式-動作)
單一分支模式語法:
expect "hi" { send "you said hi \n"}
匹配到hi後,會輸出"you said hi",並換行
多分支模式語法:
expect "hi" { send "You said hi\n" } \
"hehe" { send "Hehe yourself\n" } \
"bye" { send "Good bye\n" }
? 匹配hi,hello,bye任意字符串時,執行相應輸出。等同如下:
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send "Good bye\n"}
}
expect 示例:
ssh自動鍵入密碼
#!/usr/bin/expect
spawn ssh 192.168.65.132
expect {
"yes/no" { send "yes\n";exp_contunue }
"password" { send "112233\n"; }
}
interact
#expect eof
scp自動鍵入密碼
#!/usr/bin/expect
spawn scp /etc/passwd 192.168.65.132:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" {send "112233\n" }
}
expect eof
自動從文件獲取ip地址,且登錄ip地址機器的root賬號,並創建賬號
也可以不用條用,直接在bash腳本中引用expect代碼
cat ssh.exp
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd user1\n" }
expect "]#" { send "echo nagedu |passwd --stdin user1\n"}
send "exit\n"
#interact
expect eof
cat sshauto.sh
#!/bin/bash
while read ip;do
user=root
password=112233
ssh.exp $ip $user $password
done < /root/bin/ip.txt
自動從文件獲取ip地址,並scp同一文件到所有主機的root目錄.txt
#!/bin/bash
declare password=112233
while read ip;do
expect <<EOF
spawn scp /root/bin/scpauto.sh $ip:/root
expect {
"password" { send "112233\n" }
}
expect eof
EOF
done < /root/bin/ip.txt
筆記整理完成時間:2018年5月16日20:21:12
第14章,Shell腳本編程進階