1. 程式人生 > >19.Shell編程進階,數組,字符串(for,select,while read line,dec

19.Shell編程進階,數組,字符串(for,select,while read line,dec

追加元素 code glob arguments rcfile 變量初始化 引用 變量賦值 option

for循環

for 變量名 in 列表;do
循環體
done

  • 執行機制:
    依次將列表中的元素賦值給“變量名” ; 每次賦值後即執行一次循環體; 直到列表中的元素耗盡,循環結束

列表生成方式:

列表生成方式有多重,詳情可查看其他博客所總結。

  1. 直接給出列表
  2. 整數列表:
    (a) {start..end}
    (b) $(seq [start [step]] end)
  3. 返回列表的命令
    $(COMMAND)
  4. 使用glob通配符,如:*.sh
  5. 變量引用
    [email protected], $*

for特殊格式

雙小括號方法,即((…))格式,也可以用於算術運算
雙小括號方法也可以使bash Shell實現C語言風格的變量操作

I=10
((I++))

  • for循環的特殊格式:
    for ((控制變量初始化;條件判斷表達式;控制變量的修正表達式))
    do
    循環體
    done

  • 控制變量初始化:僅在運行到循環代碼段時執行一次
  • 控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而後再做條件判斷

這種寫法雙小括號內的判斷是按照C語言風格,而不是-eq 這類shell中test風格的

while循環

while CONDITION; do
循環體
done

  • CONDITION:循環控制條件;進入循環之前,先做一次判斷;每一次循環之後會再次做判斷;條件為“true”,則執行一次循環;直到條件測試狀態為“false”終止循環
  • 因此:CONDTION一般應該有循環控制變量;而此變量的值會在循環體不斷地被修正

進入條件:CONDITION為true
退出條件:CONDITION為false

until循環

until CONDITION; do
循環體
done

  • 進入條件: CONDITION 為false
  • 退出條件: CONDITION 為true

循環控制語句

continue

用於循環體中
continue [N]:提前結束第N層的本輪循環,而直接進入下一輪判斷;最內層為第1層

while CONDTIITON1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done

break

用於循環體中
break [N]:提前結束第N層循環,最內層為第1層
while CONDTIITON1; do

CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done

註意點:

  1. continue只是結束此這一次循環(默認最內層),然後繼續下一次循環。它仍然在此循環內,只是continue下面的語句在此次循環中不再執行了。如果後面加上數字,就是跳到數字所在的循環層數內繼續執行數字所在層的後面所剩的循環次數(根據判斷條件判斷的),而比數字小的循環則不再執行(相當於break掉了比數字小的循環)
  2. break相當於此循環直接停止並跳出(默認最內層),後面加上數字就代表了它直接跳出數字所在層的循環(跳到數字所在層的更外一層,然後執行更外一層的命令,而continue是跳到數字所在層繼續執行數字所在層的剩下的循環)。
  3. 註意break只是循環退出了,並是不exit腳本退出。
  4. 還有一點就是假如有2層或者多層循環,break 退出當前層循環之後它是繼續執行外層循環中在當前循環後面的命令,而contine 2則跳到外層循環之後直接從外層循環的開頭重新進行下一輪循環了。

shift命令

shift [n]
用於將參量列表 list 左移指定次數,缺省為左移一次。
參量列表 list 一旦被移動,最左端的那個參數就從列表中刪除。 while 循環遍歷位置參量列表時,常用到 shift
例如:
./doit.sh a b c d e f g h
./shfit.sh a b c d e f g h

示例:doit.sh
#!/bin/bash
# Name: doit.sh
# Purpose: shift through command line arguments
# Usage: doit.sh [args]
while [ $# -gt 0 ] # or while (( $# > 0 ))
do
echo $*
shift
done

示例:shift.sh
#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
echo "$1"
shift
done
echo

創建無限循環

while true; do
循環體
done

until false; do
循環體
Done

特殊用法(很實用)

遍歷文件每一行這個寫法很實用,參考自己寫的創建用戶的腳本讀入每一行的命令就知道為何實用了(因為它只把換行符當做分隔符,而不把空格當做分隔符,每一行當做一個長字符串存入變量中)

while循環的特殊用法(遍歷文件的每一行)

while read line; do
循環體
done < /PATH/FROM/SOMEFILE

  • 依次讀取/PATH/FROM/SOMEFILE文件中的每一行,且將行賦值給變量line

select循環與菜單

PS3="Please input a number:"
select variable in list
do
循環體命令
done

  • select 循環主要用於創建菜單,按數字順序排列的菜單項將顯示在標準錯誤上,並顯示 PS3 提示符,等待用戶輸入
  • 用戶輸入菜單列表中的某個數字,執行相應的命令
  • 用戶輸入被保存在內置變量 REPLY 中(和read的一樣)
  • select 是個無限循環,因此要記住用 break 命令退出循環,或用 exit 命令終止腳本。也可以按 ctrl+c 退出循環
  • select 經常和 case 聯合使用
    註意 :與 for 循環類似,可以省略 in list,此時使用位置參量
PS3="please input a number:"
select menu in backup clean config start stop status restart quit;do
        case $menu in
        *)
                echo $REPLY
                echo $menu;;
        esac
done      

函數介紹

  1. 函數function是由若幹條shell命令組成的語句塊,實現代碼重用和模塊化編程
  2. 它與shell程序形式上是相似的,不同的是它不是一個單獨的進程,不能獨立運行,而是shell程序的一部分

函數和shell程序比較相似,區別在於

  1. Shell程序在子Shell中運行
  2. 而Shell函數在當前Shell中運行。因此在當前Shell中,函數可以對shell中變量進行修改

定義函數

函數由兩部分組成:函數名和函數體
help function
語法一:
f_name (){
...函數體...
}
語法二:
function f_name {
...函數體...
}
語法三:
function f_name () {
...函數體...
}

函數使用

  1. 函數的定義和使用:
    可在交互式環境下定義函數
    可將函數放在腳本文件中作為它的一部分
    可放在只包含函數的單獨文件中
  2. 調用:函數只有被調用才會執行
    調用:給定函數名
    函數名出現的地方,會被自動替換為函數代碼
  3. 函數的生命周期:被調用時創建,返回時終止

函數返回值

函數有兩種返回值:

  1. 函數的執行結果返回值:
    (1) 使用echo等命令進行輸出
    (2) 函數體中調用命令的輸出結果
  2. 函數的退出狀態碼:
    (1) 默認取決於函數中執行的最後一條命令的退出狀態碼
    (2) 自定義退出狀態碼,其格式為:
    return 從函數中返回,用最後狀態命令決定返回值
    return 0 無錯誤返回
    return 1-255 有錯誤返回

環境函數

使子進程也可使用
聲明:export -f function_name
查看:export -f 或 declare -xf

函數遞歸示例

  • 函數遞歸:
    函數直接或間接調用自身
    註意遞歸層數
  • 示例:階乘

fork×××

fork×××是一種惡意程序,它的內部是一個不斷在fork進程的無限循環,實質是一個簡單的遞歸程序。由於程序是遞歸的,如果沒有任何限制,這會導致這個簡單的程序迅速耗盡系統裏面的所有資源

  • 函數實現
    :(){ :|:& };:
    bomb() { bomb | bomb & }; bomb
  • 腳本實現
    cat Bomb.sh
    #!/bin/bash
    ./$0|./$0&
func_factorial(){
        local input=$1
        if func_ispositint $input ;then
                [ $input -gt 1 ] && echo $[`func_factorial $[input-1]`*input] || echo 1
        else
                echo -e "Please input a positive integer"
                return 1 
        fi      
}                  

註意知識點:

  1. 函數就像一個普通命令,聲明定義之後(source之後)直接使用即可,看不出和命令的區別。因此命名函數的時候最好以func_funcname的方式進行命名加以區分。
  2. 刪除函數和刪除變量一樣 用unset func_name命令即可
  3. 註意下面這兩個命令,它倆顯示是已經加載入內存中正在被引用的函數

    • declare -F :只看函數名,後面可以跟函數名查詢特定的函數,下同
    • declare -f : 看函數的定義內容,也就是/etc/init.d/functions文件中的內容
  4. 可以把自己常用的函數寫在一個文件裏,然後source它引用。
    比如系統中/etc/init.d/function中的actions()函數就可拿來直接用,先source或者.它即可.註意腳本中也要source它,這樣它才能在開啟的子shell中也運行。
action string true :它就會顯示正確結果  action string false : 它就會顯示錯誤結果
  1. 註意因為函數是在當前shell中運行,因此它的變量會影響當前腳本或者shell中的變量結果(也就是兩個變量名字相同會造成變量汙染),因此 函數中的變量一般都定義為 local variable=賦值;

    • 三種常見變量1.普通變量直接賦值無需定義,當前shell中有效;2.export 全局變量(環境變量) 可以定義的時候賦值,也可以普通變量賦值完之後再定義,子shell和當前shell有效;3.局部變量local 函數中使用
  2. 三個返回:return 返回出函數;break,continue 返回出循環;exit 返回出shell(腳本) ;

    • 一個等待:wait 後臺命令結束後自動返回前臺並打印PS1提示符
    • 註意所有的返回都是返回到執行這個函數,腳本,循環的上一層環境命令處,返回命令後面的命令(在這個函數,腳本,循環中)則不再執行了。
  3. { cmd; cdm;} 也可以看作是一個匿名函數(沒有名字的函數,只能調用一次),更詳細的關於括號的解釋參考其他博客。
  4. return 後面不加數字的話就是返回上面的最後一個命令的$?值作為這整個函數的返回值,如果加上數字則把這個數字當做這個函數整體的返回值$?.
  5. 函數類似腳本後面也可以跟參數,用法一模一樣。註意$*和[email protected]的區別,加上雙引號引用此參數傳遞到子函數或者子腳本中,前者代表一整個字符串作為一個參數,後者代表分別作為一個一個參數。不加上雙引號沒區別,這也是因為不加雙引號會把裏面的空格作為分隔符。

    • 註意如果是腳本中調用函數,則腳本後面的參數調入到函數中時,如若函數後的參數是腳本所用的參數,一定要註意參數用$加數字方式的時候,寫好順序。同時如果想要函數對這些參數進行改變,一定註意函數內部的變量不要定義為局部變量才可。
  6. 函數如果寫在腳本中要寫在最前面以供後面的命令調用,註意shell中的函數沒有先聲明後定義的用法,只能寫在最前面,不像C語言可以先聲明後面再定義。
  7. 註意函數遞歸是先遞歸到最裏層,然後最裏層返回一個返回值,然後在從最裏層往外返回到最外層。因此最裏層一定要寫好返回值的條件,不然會永遠遞歸下去消耗系統資源。遞歸×××就從此而來。
  8. 通過階乘的編寫需要註意遞歸函數的編寫:
    • return返回的值是函數的返回值存在$?中,不能作為函數名字的結果被上一層遞歸計算
    • 用echo 返回出值,然後就可以代表這個函數名字當前層級的值,可以被上一層級來利用它進行計算
    • 註意echo 返回值不能寫在最後,只能寫在遞歸的判斷裏面,否則每次遞歸都會echo 一次,會導致每次遞歸函數名的返回值是兩個(2層) 3個(三層).... 這樣的話就沒辦法把函數本身當做一個數值來進行計算了。
    • 最底層沒有返回結果則會永遠遞歸下去,不斷的開子進程,folk×××就是利用此

信號捕捉trap

  1. trap ‘觸發指令‘ 信號
    進程收到系統發出的指定信號後,將執行自定義指令,而不會執行原操作
  2. trap ‘‘ 信號
    忽略信號的操作
  3. trap ‘-‘ 信號
    恢復原信號的操作
  4. trap -p
    列出自定義信號操作
  5. trap finish EXIT
    當腳本退出時,執行finish函數

註意點:

  1. trap的用處就是在shell(主要體現在腳本)中,讓某些命令無法進行操作,比如說kill -9 命令等等。可以保證腳本的各種運行狀態
  2. 還有一種情況就是腳本執行一半因為某些特殊情況非正常退出,或者說遇到某些條件判斷退出了,則在退出的時候捕獲到退出信號EXIT(EXIT信號並未在kill -l中列出),則會執行退出時指定的命令或者函數(需要腳本前面提前定義函數等)(If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell.)
  3. trap的生效在腳本中也是從前到後的,因此trap的信號捕獲命令一定要寫在腳本的最前端。同時後面的信號可以寫對應的數字標號,特殊的0表示shell(腳本)退出信號。
  4. 註意下面trap中寫的echo命令會打斷sleep,如果想要不打斷,則中間的命令部分什麽也不寫空著最好,這樣int命令將沒辦法執行任何操作了。

trap示例

#!/bin/bash
trap ‘echo “signal:SIGINT"‘ int
trap -p

for((i=0;i<=10;i++))
do
sleep 1
echo $i
done

trap ‘‘ int
trap -p

for((i=11;i<=20;i++))
do
sleep 1
echo $i
done

trap ‘-‘ int
trap -p

for((i=21;i<=30;i++))
do
sleep 1
echo $i
done

數組

  1. 變量:存儲單個元素的內存空間
  2. 數組:存儲多個元素的連續的內存空間,相當於多個變量的集合
  3. 數組名和索引
    索引:編號從0開始,屬於數值索引
    • 註意:索引可支持使用自定義的格式,而不僅是數值格式,即為關聯索引,從bash4.0版本之後開始支持

聲明數組:

declare -a ARRAY_NAME 普通數組:可以不用先聲明,直接使用即可
declare -A ARRAY_NAME 關聯數組:必須先聲明才可以使用,如果先使用了,必須unset之後再重新聲明才可以使用
註意:兩者不可相互轉換

數組註意點:

  1. bash的數組支持稀疏格式(索引不連續),非常實用,但是要註意稀疏格式的數組總個數就是有元素的個數,而不是數組的下標的最大值的個數
  2. 註意字符串(也就是變量名)取長度的時候不要加下標${#varialble},只有包含多個元素的數組取長度的時候才加下標${#array[*]}。同時字符串想要取中間的部分字符,只能用${var:#:#}的這種方式,因為var它就相當於數組只有一個元素它本身(或者說這個字符串開頭的地址var[0])
  3. 而數組中的每個元素中內容想要切片的話則要加上下標${array[INDEX]:#:#},(此時就是把這個帶下標的數組元素名當做一個地址,看作是本身的下標0了)。取數組中的各個元素的話則是${array[*|@]:#:#}的方式。
  4. 不寫下標默認為0,因此字符串數組(也就是變量)就是代表整個字符串,而數組就是代表數組裏面的第一個元素。
17:47[[email protected] /etc/sysconfig/network-scripts]# abc=12334
17:54[[email protected] /etc/sysconfig/network-scripts]# echo ${abc[0]}
12334
17:54[[email protected] /etc/sysconfig/network-scripts]# echo ${abc}
12334
17:54[[email protected] /etc/sysconfig/network-scripts]# echo ${#abc}
5

17:57[[email protected] /data/scriptest]# array=([1]=12wad [5]=ff [6]=cvv [8]=1wd234)
17:57[[email protected] /data/scriptest]# echo $array

17:57[[email protected] /data/scriptest]# echo ${array[0]}

17:58[[email protected] /data/scriptest]# echo ${array[1]}
12wad
17:58[[email protected] /data/scriptest]# echo ${#array[1]}  :第一個元素的長度
5
17:58[[email protected] /data/scriptest]# echo ${#array[*]}  :數組總長度,稀疏數組只看有幾個元素就是幾
4
17:58[[email protected] /data/scriptest]# echo ${array[1]:2:3} :第一個元素內容切片
wad
17:58[[email protected] /data/scriptest]# echo ${array[*]}
12wad ff cvv 1wd234
20:00[[email protected] /data/scriptest]# echo ${array[*]:2:2} :取不同的數組元素,註意和元素內容切片的區分
ff cvv
20:00[[email protected] /data/scriptest]# echo ${array[*]:2:3}
ff cvv 1wd234

數組賦值

數組元素的賦值

(1) 一次只賦值一個元素
ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday"
weekdays[4]="Thursday"
(2) 一次賦值全部元素(註意盡量要加上引號,雖然空格也可以作為分隔符)
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
(3) 只賦值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
(4) 交互式數組值對賦值(註意有一個-a的選項)
read -a ARRAY

顯示所有數組:declare -a

特殊的賦值方式:

num=({1..10}) :1到10賦值給num[0]到num[9]
file=(*.sh) :當前目錄中sh後綴的文件名賦值給file數組

稀疏方式賦值註意:如果先一個一個賦值,然後又一次賦值特定元素(3中的方式),就算賦值的元素下標並不一樣,但這樣仍然則會將一個一個賦值的結果給覆蓋掉。但是反過來如果先一次性賦值(3中的方式),然後再一個一個賦值下標不同的數組元素,則不會覆蓋掉一次性賦值的元素例子:

19:40[[email protected] /data]# testarray[2]=12
19:40[[email protected] /data]# testarray[5]=1234
19:40[[email protected] /data]# echo ${testarray[2]} 
12
19:40[[email protected] /data]# declare -a
declare -a testarray=‘( [2]="12" [5]="1234")‘
19:40[[email protected] /data]# testarray=( [1]=wade [3]=waef )
19:40[[email protected] /data]# echo ${testarray[2]} 

19:40[[email protected] /data]# declare -a
declare -a testarray=‘([1]="wade" [3]="waef")‘
上面會覆蓋,然後繼續,下面的不會覆蓋:
19:40[[email protected] /data]# testarray[2]=12
19:44[[email protected] /data]# testarray[5]=1234
19:44[[email protected] /data]# declare -a
declare -a testarray=‘([1]="wade" [2]="12" [3]="waef" [5]="1234")‘

引用數組

  1. 引用數組元素
    ${ARRAY_NAME[INDEX]}
    • 註意:省略[INDEX]表示引用下標為0的元素
  2. 引用數組所有元素
    ${ARRAY_NAME[*]}
    ${ARRAY_NAME[@]}
  3. 數組的長度(數組中元素的個數)
    ${#ARRAY_NAME[*]}
    ${#ARRAY_NAME[@]}
  4. 刪除數組中的某元素:導致稀疏格式
    unset ARRAY[INDEX]
  5. 刪除整個數組(類似的還有刪除變量,刪除函數,註意都是從內存中刪除掉)
    unset ARRAY

數組數據處理

引用數組中的元素:

  1. 數組切片:
    ${ARRAY[*]:offset:number}
    offset 要跳過的元素個數
    number 要取出的元素個數
  2. 取偏移量之後的所有元素
    ${ARRAY[*]:offset}
  3. 向數組中追加元素(因為下標從0開始):
    ARRAY[${#ARRAY[*]}]=value
  4. 關聯數組:
    declare -A ARRAY_NAME
    ARRAY_NAME=([idx_name1]=‘val1‘ [idx_name2]=‘val2‘...)
    註意:關聯數組必須先聲明再調用

示例

  • 生成10個隨機數保存於數組中,並找出其最大值和最小值
    (優化算法:不要每次都判斷是否是第一個,把第一個賦值寫在for循環外面,然後for循環直接從1開始即可)
    #!/bin/bash
    declare -i min max
    declare -a nums
    for ((i=0;i<10;i++));do
    nums[$i]=$RANDOM
    [ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue
    [ ${nums[$i]} -gt $max ] && max=${nums[$i]}
    [ ${nums[$i]} -lt $min ] && min=${nums[$i]}
    done
    echo “All numbers are ${nums[*]}”
    echo Max is $max
    echo Min is $min

示例

  • 編寫腳本,定義一個數組,數組中的元素對應的值是/var/log目錄下所有以.log結尾的文件;統計出其下標為偶數的文件中的行數之和
#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d‘ ‘ -f1)
fi
done
echo "Lines: $lines."

字符串切片

  1. ${#var}:返回字符串變量var的長度
  2. ${var:offset}:返回字符串變量var中從第offset個字符後(不包括第offset個字符)的字符開始,到最後的部分,offset的取值在0 到 ${#var}-1 之間(bash4.2後,允許為負值)
  3. ${var:offset:number}:返回字符串變量var中從第offset個字符後(不包括第offset個字符)的字符開始,長度為number的部分
  4. ${var: -length}:取字符串的最右側幾個字符
    註意:冒號後必須有一空白字符
  5. ${var:offset:-length}:從最左側跳過offset字符,一直向右取到距離最右側lengh個字符之前的內容
  6. ${var: -length:-offset}:先從最右側向左取到length個字符開始,再向右取到距離最右側offset個字符之間的內容
    註意:-length前有空格(中間的那個必須有空格,:最後的一項可以不加空格)

字符串處理

  1. 基於模式取子串
    ${var#*word}:其中word可以是指定的任意字符
    功能:自左而右,查找var變量所存儲的字符串中,第一次出現的word, 刪除字符串開頭至第一次出現word字符串(含)之間的所有字符
  2. ${var##*word}:同上,貪婪模式,不同的是,刪除的是字符串開頭至最後一次由word指定的字符之間的所有內容

    • 示例:
      file=“var/log/messages”
      ${file#/}: log/messages
      ${file##
      /}: messages
  3. ${var%word*}:其中word可以是指定的任意字符
    功能:自右而左,查找var變量所存儲的字符串中,第一次出現的word, 刪除字符串最後一個字符向左至第一次出現word字符串(含)之間的所有字符

    • 例子:
      file="/var/log/messages"
      ${file%/*}: /var/log
  4. ${var%%word*}:同上,只不過刪除字符串最右側的字符向左至最後一次出現word字符之間的所有字符
    • 示例:
      url=http://www.51cto.com:80
      ${url##:} 80
      ${url%%:
      } http

查找替換

  1. ${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替換之
  2. ${var//pattern/substr}:查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替換之
  3. ${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替換之
  4. ${var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替換之

查找並刪除

  1. ${var/pattern}:刪除var表示的字符串中第一次被pattern匹配到的字符串,從左往右匹配
  2. ${var//pattern}:刪除var表示的字符串中所有被pattern匹配到的字符串,從左往右匹配
  3. ${var/#pattern}:刪除var表示的字符串中所有以pattern為行首匹配到的字符串
  4. ${var/%pattern}:刪除var所表示的字符串中所有以pattern為行尾所匹配到的字符串

字符大小寫轉換

${var^^}:把var中的所有小寫字母轉換為大寫
${var,,}:把var中的所有大寫字母轉換為小寫

變量賦值的其他形式

技術分享圖片

  • 註意裏面的expr是一個字符串,不是一個變量。直接就是雙引號引起來的字符串。

高級變量用法-有類型變量

  • Shell變量一般是無類型的,但是bash Shell提供了declare和typeset兩個命令
  • 它可用於指定變量的類型,兩個命令是等價的

declare [選項] 變量名

-r 聲明或顯示只讀變量
-i 將變量定義為整型數
-a 將變量定義為數組
-A 將變量定義為關聯數組
-f 顯示已定義的所有函數名及其內容
-F 僅顯示已定義的所有函數名
-x 聲明或顯示環境變量和函數 :或者export
-l 聲明變量為小寫字母 declare –l var=UPPER :即使輸入大寫字母,也會變成小寫字母,下同相反
-u 聲明變量為大寫字母 declare –u var=lower

eval命令 :很實用,但是要會用它

eval命令將會首先掃描命令行進行所有的置換,然後再執行該命令。該命令適用於那些一次掃描無法實現其功能的變量.該命令對變量進行兩次掃描

  • 示例:
    [[email protected] ~]# CMD=whoami
    [[email protected] ~]# echo $CMD
    whoami
    [[email protected] ~]# eval $CMD
    root
    [[email protected] ~]# n=10
    [[email protected] ~]# echo {0..$n}
    {0..10}
    [[email protected] ~]# eval echo {0..$n}
    0 1 2 3 4 5 6 7 8 9 10

間接變量引用:可以增加腳本靈活性,比如變量中存放了一個命令(其實直接寫上$CMDvariable就能直接使用,要結果的話用echo加上反向單引號也能用`$CMDvariable`,)

  • 如果第一個變量的值是第二個變量的名字,從第一個變量引用第二個變量的值就稱為間接變量引用
  • variable1的值是variable2,而variable2又是變量名,variable2的值為value,
  • 間接變量引用是指通過variable1獲得變量值value的行為
    variable1=variable2
    variable2=value

  • bash Shell提供了兩種格式實現間接變量引用
    eval tempvar=\$$variable1 :註意不加\的話則會把當前shell的PID顯示出來($$)
    tempvar=${!variable1}
  • 示例:
    [[email protected] ~]# N=NAME
    [[email protected] ~]# NAME=wang
    [[email protected] ~]# N1=${!N}
    [[email protected] ~]# echo $N1
    wangxiaochun
    [[email protected] ~]# eval N2=\$$N
    [[email protected] ~]# echo $N2
    wang

創建臨時文件

mktemp命令:創建並顯示臨時文件,可避免沖突
mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filenameXXX
X至少要出現三個

  • OPTION:
    -d: 創建臨時目錄
    -p DIR或--tmpdir=DIR:指明臨時文件所存放目錄位置
  • 示例:
    mktemp /tmp/testXXX
    tmpdir=`mktemp –d /tmp/testdirXXX`
    mktemp --tmpdir=/testdir testXXXXXX

註意

  1. 它創建臨時文件的時候會有默認的輸出,因此可以把它命令的結果存入到變量中比如file=`mktemp /data/tempXXXX`
  2. 平常創臨時文件的時候直接就寫上了路徑 ,不過也可以分開寫 要難過-p 指定dir 然後後面只寫臨時文件名字就行了。不過一般不這麽麻煩
  3. 自己單獨編譯安裝的時候某些文件就要用到臨時文件

安裝復制文件

install命令:

install [OPTION]... [-T] SOURCE DEST 單文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...創建空目錄

  • 選項:
    -m MODE,默認755
    -o OWNER
    -g GROUP
  • 示例:
    install -m 700 -o wang -g admins srcfile desfile
    install –m 770 –d /testdir/installdir

註意:

  1. install相當於cp ,chown ,chgrp ,chmod的集合
  2. 如果直接install 來進行拷貝,它和cp命令相比,它會讓文件全部加上執行權限(ugo都加上)(而且它默認不是交互式,直接覆蓋)
  3. 命令用法如其名,實現腳本一鍵安裝,修改文件屬性
  4. 註意他可以直接創建一個新的空文件夾,cp沒有這功能(雖然cp拷貝的時候可以對應創建文件夾,但不能直接命令來單獨創建一個空文件夾)

expect介紹

expect 是由Don Libes基於Tcl( Tool Command Language )語言開發的,主要應用於自動化交互式操作的場景,借助 expect 處理交互的命令,可以將交互過程如:ssh登錄,ftp登錄等寫在一個腳本上,使之自動化完成。尤其適用於需要對多臺服務器執行相同操作的環境中,可以大大提高系統管理人員的工作效率

它執行的時候會捕獲屏幕上出現的關鍵字,然後根據出現的key來自動提交(輸入)內容

expect命令:需要額外安裝,默認不安裝

  • expect 語法:
    expect [選項] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
  • 選項
    -c:從命令行執行expect腳本,默認expect是交互地執行的
    示例:expect -c ‘expect "\n" {send "pressed enter\n"}
    -d:可以輸出輸出調試信息
    示例:expect -d ssh.exp
  • expect中相關命令
    spawn 啟動新的進程
    send 用於向進程發送字符串
    expect 從進程接收字符串
    interact 允許用戶交互 :比如只ssh登陸,然後登錄後繼續手工命令
    exp_continue 匹配多個字符串在執行動作後加此命令

expect最常用的語法(tcl語言:模式-動作)

  1. 單一分支模式語法:
    expect “hi” {send “You said hi\n"}
    匹配到hi後,會輸出“you said hi” ,並換行
  2. 多分支模式語法:
    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"}
      }

註意:

  1. expect是一個程序命令包,它裏面包含了一個同名的expect命令。
  2. expect默認交互式,進去之後再輸入命令(比如再輸入spawn expect等)
    示例:
示例
#!/usr/bin/expect
spawn scp /etc/fstab 192.168.8.100:/app
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "www.123\n" }
}
expect eof

#!/usr/bin/expect
spawn ssh 192.168.8.100
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "www.123\n" }
}
interact
#expect eof

示例:變量,變量設置和賦值用set,後面不加等號
#!/usr/bin/expect
set ip 192.168.8.100
set user root
set password magedu
set timeout 10
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact

示例:位置參數:變量從0開始而不是shell中的從1開始
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
#./ssh3.exp 192.168.8.100 root www

示例:執行多個命令
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo www |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 192.168.8.100 root www

示例:shell腳本調用expect,就是用多行重定向的方式來使用expect
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 20
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo www |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.100 root www

19.Shell編程進階,數組,字符串(for,select,while read line,dec