1. 程式人生 > >第14章,Shell腳本編程進階

第14章,Shell腳本編程進階

linux腳本編程 linux學習 linux

更多內容請點擊:

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腳本編程進階