天天看點

SHELL程式設計腳本基礎及進階

程式

程式
    程式:算法+資料結構
    資料:是程式的核心
    資料結構:資料在計算機中的類型群組織方式
    算法:處理資料的方式

程式程式設計風格:
    過程式:以指令為中心,資料服務于指令
    對象式:以資料為中心,指令服務于資料

shell程式:提供了程式設計能力,解釋執行
           

程式的執行方式

計算機:運作二進制指令
程式設計語言:人與計算機之間互動的語言

低級程式設計語言:
    機器:二進制的0和1的序列,稱為機器指令。與自然語言差異太大,難懂、難寫
    彙編:用一些助記符号替代機器指令,稱為彙編語言
    如:ADD A,B 将寄存器A的數與寄存器B的數相加得到的數放到寄存器A中
    彙編語言寫好的程式需要彙程式設計式轉換成機器指令
    彙編語言稍微好了解,即機器指令對應的助記符,助記符更接近自然語言

進階程式設計語言:
    編譯:進階語言-->編譯器-->機器代碼-->執行 
         C,C++
    解釋:進階語言-->執行-->解釋器-->機器代碼
         shell,python,php,JavaScript,perl
           

程式設計基本概念

程式設計邏輯處理方式:
    順序執行
    循環執行
    選擇執行

shell程式設計:過程式、解釋執行
    程式設計語言的基本結構:
         各種系統指令的組合
         資料存儲:變量、數組
         表達式:a + b
         語句:if
           

shell程式設計腳本基礎

shell腳本:
   包含一些指令或聲明,并符合一定格式的文本檔案

格式要求:首行shebang機制
   #!/bin/bash
   #!/usr/bin/python
   #!/usr/bin/perl

shell腳本的用途有:
   自動化常用指令
   執行系統管理和故障排除
   建立簡單的應用程式
   處理文本或檔案
           

建立shell腳本

第一步:使用文本編輯器來建立文本檔案
   第一行必須包括shell聲明序列:#!
       示例:#!/bin/bash
   添加注釋
        注釋以#開頭

第二步:運作腳本
   給予執行權限,在指令行上指定腳本的絕對或相對路徑
   直接運作解釋器,将腳本作為解釋器程式的參數運作

腳本規範
腳本代碼開頭約定
   1、第一行一般為調用使用的語言
   2、程式名,避免更改檔案名為無法找到正确的檔案
   3、版本号
   4、更改後的時間
   5、作者相關資訊
   6、該程式的作用,及注意事項
   7、最後是各版本的更新簡要說明

腳本的基本結構
   #!SHEBANG
   CONFIGURATION_VARIABLES
   FUNCTION_DEFINITIONS
   MAIN_CODE

shell腳本示例
#!/bin/bash
# ------------------------------------------
# Filename: hello.sh
# Revision: 1.1
# Date: 2017/06/01
# Author: wang
# Email: [email protected]
# Website: www.magedu.com
# Description: This is the first script
# Copyright: 2017 wang
# License: GPL
# ------------------------------------------
echo “hello world”

腳本調試
   檢測腳本中的文法錯誤
       bash -n /path/to/some_script
   調試執行
       bash -x /path/to/some_script
           

變量

變量:命名的記憶體空間
變量:變量類型
    作用:
        1、資料存儲方式
        2、參與的運算
        3、表示的資料範圍
    類型:
        字元
        數值:
        	精确數值
        	近似數值
        	整型、浮點型、字元型、布爾型、日期時間型

靜态編譯語言:使用變量前,先聲明變量類型,之後類型不能改變,在編譯時檢查,如:java,c 
動态編譯語言:不用事先聲明,可随時改變類型,如bash,Python
強類型語言:不同類型資料操作,必須經過強制轉換才同一類型才能運算,如java , c# ,python
    如:以下python代碼
    print(‘magedu’+ 10) 提示出錯,不會自動轉換類型
    print(‘magedu’+str(10)) 結果為magedu10,需要顯示轉換類型
弱類型語言:語言的運作時會隐式做資料類型轉換。無須指定類型,預設均為字元型;參與運算會自動進行隐式類型轉換;變量無須事先定義可直接調用
    如:bash 不支援浮點數,php,javascript

Shell中變量命名法則:
    1、不能使程式中的保留字:例如if, for
    2、隻能使用數字、字母及下劃線,且不能以數字開頭
    3、見名知義
    4、統一命名規則:駝峰命名法

Shell中命名建議規則:
    1、變量名大寫
    2、局部變量小寫
    3、函數名小寫
    4、用英文名字,并展現出實際作用
           

bash中變量的種類

根據變量的生效範圍等标準劃分下面變量類型
    局部變量:生效範圍為目前shell程序;對目前shell之外的其它shell程序,包括目前shell的子shell程序均無效
    環境變量:生效範圍為目前shell程序及其子程序
    本地變量:生效範圍為目前shell程序中某代碼片斷,通常指函數
    位置變量:$1, $2, ...來表示,用于讓腳本在腳本代碼中調用通過指令行傳遞給它的參數
    特殊變量:$?, $0, $*, [email protected], $#,$$
           

局部變量

變量指派:name=‘value’
可以使用引用value
    (1)可以是直接字串:name=“root"
    (2)變量引用:name="$USER"
    (3)指令引用:name=`COMMAND` 
               name=$(COMMAND)
變量引用:${name} 或者 $name
    " " 弱引用,其中的變量引用會被替換為變量值
    ' ' 強引用,其中的變量引用不會被替換為變量值,而保持原字元串
顯示已定義的所有變量:set
删除變量:unset name
           

環境變量

變量聲明、指派:
    export name=VALUE
    declare -x name=VALUE
變量引用:
    $name, ${name}
顯示所有環境變量:
    env
    printenv
    export
    declare -x 
删除變量:
    unset name

bash内建的環境變量
    PATH
    SHELL
    USER
    UID
    HOME
    PWD
    SHLVL
    LANG
    MAIL
    HOSTNAME
    HISTSIZE
    _ 下劃線
           

隻讀和位置變量

隻讀變量:隻能聲明,但不能修改和删除
    聲明隻讀變量:
        readonly name
        declare -r name
    檢視隻讀變量:
        readonly -p 
位置變量:在腳本代碼中調用通過指令行傳遞給腳本的參數
    $1, $2, ... 對應第1、第2等參數,shift [n]換位置
    $0 指令本身
    $* 傳遞給腳本的所有參數,全部參數合為一個字元串
    [email protected] 傳遞給腳本的所有參數,每個參數為獨立字元串
    $# 傳遞給腳本的參數的個數
    注意:[email protected] $* 隻在被雙引号包起來的時候才會有差異
    set -- 清空所有位置變量
           

退出狀态

程序使用退出狀态來報告成功或失敗
    0 代表成功,1-255代表失敗
    $? 變量儲存最近的指令退出狀态
例如:
    ping -c1 -W1 hostdown &> /dev/null 
    echo $?

退出狀态碼
bash自定義退出狀态碼
    exit [n]:自定義退出狀态碼
    注意:腳本中一旦遇到exit指令,腳本會立即終止;終止退出狀态取決于exit指令後面的數字
    注意:如果未給腳本指定退出狀态碼,整個腳本的退出狀态碼取決于腳本中執行的最後一條指令的狀态碼
           

算術運算

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(0-32767)
    示例:生成 0 - 49 之間随機數
         echo $[$RANDOM%50]
           

指派

增強型指派:
    +=, -=, *=, /=, %=
let varOPERvalue
    例如:let count+=3
    自加3後自指派
自增,自減:
    let var+=1
    let var++
    let var-=1
    let var--
           

邏輯運算

true, false
    1, 0
與:隻有全為1,結果為1,隻要有一個是0,結果就是0
    1 與 1 = 1
    1 與 0 = 0
    0 與 1 = 0
    0 與 0 = 0
或:隻要有一個1,結果為1,全為0,結果才為0
    1 或 1 = 1
    1 或 0 = 1
    0 或 1 = 1
    0 或 0 = 0
非:!
    ! 1 = 0 ! true
    ! 0 = 1 ! false
短路運算
    短路與:cmd1 && cmd2 :如果cmd1 結果為失敗,不執行cmd2 ,如果cmd1 成功,執行cmd2 
        第一個為0,結果必定為0
        第一個為1,第二個必須要參與運算
    短路或:cmd1 || cmd2: 如果cmd1 成功,不執行cmd2,如果cmd1失敗,執行cmd2
        第一個為1,結果必定為1
        第一個為0,第二個必須要參與運算
異或:^
    異或的兩個值,相同為假,不同為真
           

條件測試

判斷某需求是否滿足,需要由測試機制來實作
   專用的測試表達式需要由測試指令輔助完成測試過程
評估布爾聲明,以便用在條件性執行中
   • 若真,則傳回0 
   • 若假,則傳回1 
測試指令:
   • test EXPRESSION
   • [ EXPRESSION ] 
   • [[ EXPRESSION ]]   一般用于正規表達式的時候
   注意:EXPRESSION前後必須有空白字元
           

bash的數值測試

-v VAR 
    變量VAR是否設定
數值測試:
    -gt 是否大于
    -ge 是否大于等于
    -eq 是否等于
    -ne 是否不等于
    -lt 是否小于
    -le 是否小于等于
           

bash的字元串測試

字元串測試:
    = 是否等于
    > ascii碼是否大于ascii碼 
    < 是否小于
    != 是否不等于
    =~ 左側字元串是否能夠被右側的PATTERN所比對,意為包含
       注意: 此表達式一般用于[[ ]]中;擴充的正規表達式
    -z "STRING“ 字元串是否為空,空為真,不空為假
    -n "STRING“ 字元串是否不空,不空為真,空為假
注意:用于字元串比較時的用到的操作數都應該使用引号
           

Bash的檔案測試

存在性測試
    -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的硬連結
    FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
    FILE1 -ot FILE2: FILE1是否舊于FILE2
           

Bash的組合測試條件

第一種方式:
    EXPRESSION1 -a EXPRESSION2 并且
    EXPRESSION1 -o EXPRESSION2 或者
    ! EXPRESSION
    必須使用測試指令進行,[[ ]] 不支援
第二種方式:
    COMMAND1 && COMMAND2 并且,短路與,代表條件性的AND THEN
    COMMAND1 || COMMAND2 或者,短路或,代表條件性的OR ELSE
    ! COMMAND 非 如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]

示例:
    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“
    [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
    [ -z “$HOSTNAME” -o $HOSTNAME "=="localhost.localdomain" ] \
    && hostname www.magedu.com
           

條件性的執行操作符

示例:
    grep -q no_such_user /etc/passwd || echo 'No such user' 
    No such user

    ping -c1 -W2 station1 &> /dev/null \ 
    > && echo "station1 is up" \ 
    > > || (echo 'station1 is unreachable'; exit 1) 
    station1 is up
           

使用read指令來接受輸入

使用read來把輸入值配置設定給一個或多個shell變量
    -p 指定要顯示的提示
    -s 靜默輸入,一般用于密碼
    -n N 指定輸入的字元長度N -d ‘字元’ 輸入結束符
    -t N TIMEOUT為N秒
    read 從标準輸入中讀取值,給每個單詞配置設定一個變量
    所有剩餘單詞都被配置設定給最後一個變量
    read -p “Enter a filename: “ FILE
           

條件選擇if語句

選擇執行:
注意:if語句可嵌套

單分支
    if 判斷條件;then
        條件為真的分支代碼
    fi

雙分支
    if 判斷條件; then
        條件為真的分支代碼
    else
        條件為假的分支代碼
    fi

多分支
    if 判斷條件1; then
        條件1為真的分支代碼
    elif 判斷條件2; then
        條件2為真的分支代碼
    elif 判斷條件3; then
        條件3為真的分支代碼
    else
        以上條件都為假的分支代碼
    fi
逐條件進行判斷,第一次遇為“真”條件時,執行其分支,而後結束整個if語句

if示例:
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
           

條件判斷:case語句

case 變量引用 in
PAT1)
    分支1
    ;;
PAT2)
    分支2
    ;;
    ...
*)
    預設分支
    ;;
esac

case支援glob風格的通配符:
    *: 任意長度任意字元
    ?: 任意單個字元
    []:指定範圍内的任意單個字元
    a|b: a或b
           

bash如何展開指令行

運作指令的優先級次序:
    把指令行分成單個指令詞
    展開别名
    展開大括号的聲明({}) 
    展開波浪符聲明(~) 
    指令替換$() 和 ``) 
    再次把指令行分成指令詞
    展開檔案通配(*、?、[abc]等等)
    準備I/0重導向(<、>) 
    運作指令

防止擴充
反斜線(\)會使随後的字元按原意解釋
    echo Your cost: \$5.00 
    Your cost: $5.00
加引号來防止擴充
    • 單引号(’’)防止所有擴充
    • 雙引号(”“)也可防止擴充,但是以下情況例外:
        $(美元符号) 變量擴充
        ` ` (反引号) 指令替換
        \(反斜線) 禁止單個字元擴充
        !(歎号) 曆史指令替換
           

bash的配置檔案

按生效範圍劃分,存在兩類:
    全局配置:
        /etc/profile
        /etc/profile.d/*.sh
        /etc/bashrc
    個人配置:
        ~/.bash_profile
        ~/.bashrc
           

shell登入兩種方式

互動式登入:
    (1)直接通過終端輸入賬号密碼登入
    (2)使用“su - UserName” 切換的使用者
    執行順序:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非互動式登入:
    (1)su UserName
    (2)圖形界面下打開的終端
    (3)執行腳本
    (4)任何其它的bash執行個體
    執行順序: /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc
           

Profile類

按功能劃分,存在兩類:
    profile類和bashrc類
profile類:為互動式登入的shell提供配置
    全局:/etc/profile, /etc/profile.d/*.sh
    個人:~/.bash_profile
    功用:
    (1) 用于定義環境變量
    (2) 運作指令或腳本
           

Bashrc類

bashrc類:為非互動式和互動式登入的shell提供配置
    全局:/etc/bashrc
    個人:~/.bashrc
    功用:
    (1) 定義指令别名和函數
    (2) 定義本地變量
           

編輯配置檔案生效

修改profile和bashrc檔案後需生效
    兩種方法: 
    1重新啟動shell程序
    2 . 或source
    注意:source一般用在生效配置檔案,不用在執行腳本,會改變目前環境變量
    例:
    . ~/.bashrc
           

Bash 退出任務

儲存在~/.bash_logout檔案中(使用者)
在登出shell時運作
用于
    • 建立自動備份
    • 清除臨時檔案
           

set 指令

$- 變量
    h:hashall,打開這個選項後,Shell 會将指令所在的路徑hash下來,避免每次都要查詢。通過set +h将h選項關閉
    i:interactive-comments,包含這個選項說明目前的 shell 是一個互動式的shell。所謂的互動式shell,在腳本中,i選項是關閉的。
    m:monitor,打開監控模式,就可以通過Job control來控制程序的停止、繼續,背景或者前台執行等。
    B:braceexpand,大括号擴充
    H:history,H選項打開,可以展開曆史清單中的指令,可以通過!感歎号來完成,例如“!!”傳回上最近的一個曆史指令,“!n”傳回第 n 個曆史指令

腳本安全,建議工作中添加
set 指令
    -u 在擴充一個沒有設定的變量時,顯示錯誤資訊
       等同set –o nounset
    -e 如果一個指令傳回一個非0退出狀态值(失敗)就退出
       等同set –o errexit
           

流程控制

程式設計中的邏輯處理:
    順序執行
    選擇執行
    循環執行
           

循環

循環執行
    将某代碼段重複運作多次
    重複運作多少次
      循環次數事先已知
      循環次數事先未知
    有進入條件和退出條件
for, while, until
           

for循環

for有兩種文法,可用 help 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
控制變量初始化:僅在運作到循環代碼段時執行一次
控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而後再做條件判斷

練習:用for實作
1、判斷/var/目錄下所有檔案的類型
2、添加10個使用者user1-user10,密碼為8位随機字元
3、/etc/rc.d/rc3.d目錄下分别有多個以K開頭和以S開頭的檔案;分别讀取每個檔案,以K開頭的輸出為檔案加stop,以S開頭的輸出為檔案名加start,如K34filename stop S66filename start
4、編寫腳本,提示輸入正整數n的值,計算1+2+…+n的總和
5、計算100以内所有能被3整除的整數之和
6、編寫腳本,提示請輸入網絡位址,如192.168.0.0,判斷輸入的網段中主機線上狀态
7、列印九九乘法表
8、在/testdir目錄下建立10個html檔案,檔案名格式為數字N(從1到10)加随機8個字母,如:1AbCdeFgH.html
9、列印等腰三角形
10、猴子第一天摘下若幹個桃子,當即吃了一半,還不瘾,又多吃了一個第二天早上又将剩下的桃子吃掉一半,又多吃了一個。以後每天早上都吃了前一天剩下的一半零一個。到第10天早上想再吃時,隻剩下一個桃子了。求第一天共摘了多少?
           

while循環

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

練習:用while實作
1、編寫腳本,求100以内所有正奇數之和
2、編寫腳本,提示請輸入網絡位址,如192.168.0.0,判斷輸入的網段中主機線上狀态,并統計線上和離線主機各多少
3、編寫腳本,列印九九乘法表
4、編寫腳本,利用變量RANDOM生成10個随機數字,輸出這個10數字,并顯示其中的最大值和最小值
5、編寫腳本,實作列印國際象棋棋盤
6、後續六個字元串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通過對随機數變量RANDOM随機執行指令: echo $RANDOM|md5sum|cut –c1-10 後的結果,請破解這些字元串對應的RANDOM值
           

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
           

循環控制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 (( $# > 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
           

練習

1、每隔3秒鐘到系統上擷取已經登入的使用者的資訊;如果發現使用者hacker登入,則将登入時間和主機記錄于日志/var/log/login.log中,并退出腳本
2、随機生成10以内的數字,實作猜字遊戲,提示比較大或小,相等則退出
3、用檔案名做為參數,統計所有參數檔案的總行數
4、用二個以上的數字為參數,顯示其中的最大值和最小值
           

特殊用法

while循環的特殊用法(周遊檔案的每一行)
    while read line; do
        循環體
    done < /PATH/FROM/SOMEFILE
依次讀取/PATH/FROM/SOMEFILE檔案中的每一行,且将行指派給變量line

練習
掃描/etc/passwd檔案每一行,如發現GECOS字段為空,則将使用者名和機關電話為62985600填充至GECOS字段,并提示該使用者的GECOS資訊修改成功
           

select循環與菜單

select variable in list 
    do 
        循環體指令
    done
select 循環主要用于建立菜單,按數字順序排列的菜單項将顯示在标準錯誤上,并顯示 PS3 提示符,等待使用者輸入
使用者輸入菜單清單中的某個數字,執行相應的指令
使用者輸入被儲存在内置變量 REPLY 中 select 是個無限循環,是以要記住用 break 指令退出循環,或用 exit 指令終止腳本。也可以按 ctrl+c 退出循環
select 經常和 case 聯合使用
與 for 循環類似,可以省略 in list,此時使用位置參量
           

函數介紹

函數function是由若幹條shell指令組成的語句塊,實作代碼重用和子產品化程式設計
它與shell程式形式上是相似的,不同的是它不是一個單獨的程序,不能獨立運作,而是shell程式的一部分
函數和shell程式比較相似,差別在于
    Shell程式在子Shell中運作
    而Shell函數在目前Shell中運作。是以在目前Shell中,函數可以對shell中變量進行修改
           

定義函數

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

函數使用

函數的定義和使用:
    可在互動式環境下定義函數
    可将函數放在腳本檔案中作為它的一部分
    可放在隻包含函數的單獨檔案中
調用:函數隻有被調用才會執行
    調用:給定函數名
    函數名出現的地方,會被自動替換為函數代碼
函數的生命周期:被調用時建立,傳回時終止
           

函數傳回值

函數有兩種傳回值:
函數的執行結果傳回值:
    (1) 使用echo等指令進行輸出
    (2) 函數體中調用指令的輸出結果
函數的退出狀态碼:
    (1) 預設取決于函數中執行的最後一條指令的退出狀态碼
    (2) 自定義退出狀态碼,其格式為:
    return 從函數中傳回,用最後狀态指令決定傳回值
    return 0 無錯誤傳回
    return 1-255 有錯誤傳回
           

互動式環境下定義和使用函數

示例:
    dir() {
    > ls -l
    > }
定義該函數後,若在$後面鍵入dir,其顯示結果同ls -l的作用相同
    dir
該dir函數将一直保留到使用者從系統退出,或執行了如下所示的unset指令
    unset dir
           

在腳本中定義及使用函數

函數在使用前必須定義,是以應将函數定義放在腳本開始部分,直至shell首次發現它後才能使用
調用函數僅使用其函數名即可
示例:
   cat func1
   #!/bin/bash
   # func1
   hello()
   {
    echo "Hello there today's date is `date +%F`"
   }
   echo "now going to the function hello"
   hello
   echo “back from the function”
           

使用函數檔案

可以将經常使用的函數存入函數檔案,然後将函數檔案載入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指令将在shell中顯示所有的載入函數
示例:
   set
    findit=( )
    {
    if [ $# -lt 1 ]; then
    echo "usage :findit file";
    return 1
    fi
    find / -name $1 -print
    }
    …
           

執行shell函數

要執行函數,簡單地鍵入函數名即可
示例:
   findit groups
   /usr/bin/groups
   /usr/local/backups/groups.bak
   ```
           

删除shell函數

現在對函數做一些改動後,需要先删除函數,使其對shell不可用。使用unset指令完成删除函數
指令格式為:
    unset function_name
示例:
    unset findit
    再鍵入set指令,函數将不再顯示
環境函數
    使子程序也可使用
    聲明:export -f function_name
    檢視:export -f 或 declare -xf
           

函數參數

函數可以接受參數:
    傳遞參數給函數:調用函數時,在函數名後面以空白分隔給定參數清單即可;
      例如“testfunc arg1 arg2 ...”
    在函數體中當中,可使用$1, $2, ...調用這些參數;還可以使用[email protected], $*, $#等特殊變量
           

函數變量

變量作用域:
    環境變量:目前shell和子shell有效
    本地變量:隻在目前shell程序有效,為執行腳本會啟動專用子shell程序;是以,本地變量的作用範圍是目前shell腳本程式檔案,包括腳本中的函數
    局部變量:函數的生命周期;函數結束時變量被自動銷毀
注意:如果函數中有局部變量,如果其名稱同本地變量,使用局部變量
在函數中定義局部變量的方法
    local NAME=VALUE
           

函數遞歸示例

函數遞歸:
    函數直接或間接調用自身
    注意遞歸層數
遞歸執行個體:
    階乘是基斯頓·卡曼于 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
    #
    fact() {
     if [ $1 -eq 0 -o $1 -eq 1 ]; then
        echo 1
     else
        echo $[$1*$(fact $[$1-1])]
     fi
    }
    fact $1
           

fork炸彈

fork炸彈是一種惡意程式,它的内部是一個不斷在fork程序的無限循環,實質是一個簡單的遞歸程式。由于程式是遞歸的,如果沒有任何限制,這會導緻這個簡單的程式迅速耗盡系統裡面的所有資源
函數實作
     :(){ :|:& };:
     bomb() { bomb | bomb & }; bomb
腳本實作
     cat Bomb.sh
     #!/bin/bash
     ./$0|./$0&
           

練習

編寫函數,實作OS的版本判斷
編寫函數,實作取出目前系統eth0的IP位址
編寫函數,實作列印綠色OK和紅色FAILED
編寫函數,實作判斷是否無位置參數,如無參數,提示錯誤

編寫服務腳本/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...”
   (7)在所有模式下禁止啟動該服務,可用chkconfig 和 service指令管理
       說明:SCRIPT_NAME為目前腳本名

編寫腳本/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/ldlinux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5)每次複制完成一個指令後,不要退出,而是提示使用者鍵入新的要複制的指令,并重複完成上述功能;直到使用者輸入quit退出

編寫函數實作兩個數字做為參數,傳回最大值
   斐波那契數列又稱黃金分割數列,因數學家列昂納多·斐波那契以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的是這樣一個數列:0、1、1、2、3、5、 8、13、21、34、……,斐波納契數列以如下被以遞歸的方法定義:F(0)=0, F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)
   利用函數,求n階斐波那契數列

   漢諾塔(又稱河内塔)問題是源于印度一個古老傳說。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天指令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。并且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次隻能移動一個圓盤,利用函數,實作N片盤的漢諾塔的移動步驟
           

信号捕捉trap

trap '觸發指令' 信号
    程序收到系統發出的指定信号後,将執行自定義指令,而不會執行原操作
trap '' 信号
    忽略信号的操作
trap '-' 信号
    恢複原信号的操作
trap -p
    列出自定義信号操作
trap finish EXIT 
    當腳本退出時,執行finish函數

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
           

數組

變量:存儲單個元素的記憶體空間
數組:存儲多個元素的連續的記憶體空間,相當于多個變量的集合
數組名和索引
    索引:編号從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]="VAL1" [3]="VAL2" ...)
    (4) 互動式數組值對指派
    read -a ARRAY
顯示所有數組:declare -a
           

引用數組

引用數組元素
    ${ARRAY_NAME[INDEX]}
    注意:省略[INDEX]表示引用下标為0的元素
引用數組所有元素
    ${ARRAY_NAME[*]}
    ${ARRAY_NAME[@]}
數組的長度(數組中元素的個數)
    ${#ARRAY_NAME[*]}
    ${#ARRAY_NAME[@]}
删除數組中的某元素:導緻稀疏格式
    unset ARRAY[INDEX]
删除整個數組
    unset ARRAY
           

數組資料處理

引用數組中的元素:
    數組切片:
         ${ARRAY[@]:offset:number}
            offset 要跳過的元素個數
            number 要取出的元素個數
        取偏移量之後的所有元素
        ${ARRAY[@]:offset}
向數組中追加元素:
    ARRAY[${#ARRAY[*]}]=value
關聯數組:
    declare -A ARRAY_NAME 
    ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
    注意:關聯數組必須先聲明再調用
           

示例

生成10個随機數儲存于數組中,并找出其最大值和最小值
     #!/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."
           

練習

輸入若幹個數值存入數組中,采用冒泡算法進行升序或降序排序
将下圖所示,實作轉置矩陣matrix.sh
1 2 3         1 4 7
4 5 6  ===>   2 5 8
7 8 9         3 6 9 
列印楊輝三角形
           

字元串切片

${#var}:傳回字元串變量var的長度
${var:offset}:傳回字元串變量var中從第offset個字元後(不包括第offset個字元)的字元開始,到最後的部分,offset的取值在0 到 ${#var}-1 之間(bash4.2後,允許為負值)  ${var:offset:number}:傳回字元串變量var中從第offset個字元後(不包括第offset個字元)的字元開始,長度為number的部分
${var: -length}:取字元串的最右側幾個字元
    注意:冒号後必須有一空白字元
${var:offset:-length}:從最左側跳過offset字元,一直向右取到距離最右側lengh個字元之前的内容
${var: -length:-offset}:先從最右側向左取到length個字元開始,再向右取到距離最右側offset個字元之間的内容
    注意:-length前空格
           

字元串處理

基于模式取子串
    ${var#*word}:其中word可以是指定的任意字元
    功能:自左而右,查找var變量所存儲的字元串中,第一次出現的word, 删除字元串開頭至第一次出現word字元串(含)之間的所有字元
    ${var##*word}:同上,貪婪模式,不同的是,删除的是字元串開頭至最後一次由word指定的字元之間的所有内容
示例:
    file=“var/log/messages”
    ${file#*/}: log/messages
    ${file##*/}: messages

${var%word*}:其中word可以是指定的任意字元
    功能:自右而左,查找var變量所存儲的字元串中,第一次出現的word, 删除字元串最後一個字元向左至第一次出現word字元串(含)之間的所有字元
    file="/var/log/messages"
    ${file%/*}: /var/log
${var%%word*}:同上,隻不過删除字元串最右側的字元向左至最後一次出現word字元之間的所有字元
示例:
    url=http://www.magedu.com:80
    ${url##*:} 80
    ${url%%:*} http

查找替換
${var/pattern/substr}:查找var所表示的字元串中,第一次被pattern所比對到的字元串,以substr替換之
${var//pattern/substr}: 查找var所表示的字元串中,所有能被pattern所比對到的字元串,以substr替換之
${var/#pattern/substr}:查找var所表示的字元串中,行首被pattern所比對到的字元串,以substr替換之
${var/%pattern/substr}:查找var所表示的字元串中,行尾被pattern所比對到的字元串,以substr替換之

查找并删除
    ${var/pattern}:删除var表示的字元串中第一次被pattern比對到的字元串
    ${var//pattern}:删除var表示的字元串中所有被pattern比對到的字元串
    ${var/#pattern}:删除var表示的字元串中所有以pattern為行首比對到的字元串
    ${var/%pattern}:删除var所表示的字元串中所有以pattern為行尾所比對到的字元串
字元大小寫轉換
    ${var^^}:把var中的所有小寫字母轉換為大寫
    ${var,,}:把var中的所有大寫字母轉換為小寫
           

變量指派

SHELL程式設計腳本基礎及進階

進階變量用法-有類型變量

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

declare [選項] 變量名
    -r 聲明或顯示隻讀變量
    -i 将變量定義為整型數
    -a 将變量定義為數組
    -A 将變量定義為關聯數組
    -f 顯示已定義的所有函數名及其内容
    -F 僅顯示已定義的所有函數名
    -x 聲明或顯示環境變量和函數
    -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
           

間接變量引用

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

bash Shell提供了兩種格式實作間接變量引用
   eval tempvar=\$$variable1
   tempvar=${!variable1}
示例:
   [[email protected] ~]# N=NAME
   [[email protected] ~]# NAME=wangxiaochun
   [[email protected] ~]# N1=${!N}
   [[email protected] ~]# echo $N1
     wangxiaochun
   [[email protected] ~]# eval N2=\$$N
   [[email protected] ~]# echo $N2
     wangxiaochun
           

建立臨時檔案

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
           

安裝複制檔案

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
           

expect介紹

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

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 允許使用者互動
    exp_continue 比對多個字元串在執行動作後加此指令

expect
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"}
    }

示例
    #!/usr/bin/expect
    spawn scp /etc/fstab 192.168.8.100:/app
    expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "magedu\n" }
    }
expect eof

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

示例:變量
    #!/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

示例:位置參數
    #!/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 magedu

示例:執行多個指令
    #!/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 magedu |passwd --stdin haha\n" }
    send "exit\n"
expect eof
#./ssh4.exp 192.168.8.100 root magedu

示例:shell腳本調用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 magedu |passwd --stdin hehe\n" }
    expect "]#" { send "exit\n" }
    expect eof
    EOF 
#./ssh5.sh 192.168.8.100 root magedu