shell腳本編程
本章內容
編程基礎 腳本基本格式 變量 運算 條件測試 流程控制 函數 數組 高級字符串操作 高級變量 配置用戶環境
編程基礎
程序:指令+數據 編程程序風格: 過程式:以指令為中心,數據服務于指令 對象式:以數據為中心,指令服務于數據 shell程序:提供了編程能力,解釋執行
程序的執行方式
? 計算機:運行二進制指令; ? 編程語言: 低級:匯編 高級: 編譯:高級語言-->編譯器-->目標代碼 java,C# 解釋:高級語言-->解釋器-->機器代碼 shell, perl, python
編程基本概念
? 編程邏輯處理方式: 順序執行 循環執行 選擇執行 ? shell編程:過程式、解釋執行 編程語言的基本結構: 數據存儲:變量、數組 表達式: a + b 語句:if
shell腳本基礎
? shell腳本是包含一些命令或聲明,并符合一定格式的文 本文件 ? 格式要求:首行shebang機制 #!/bin/bash #!/usr/bin/python #!/usr/bin/perl ? shell腳本的用途有: ? 自動化常用命令 ? 執行系統管理和故障排除 ? 創建簡單的應用程序 ? 處理文本或文件
創建shell腳本
? 第一步:使用文本編輯器來創建文本文件 ? 第一行必須包括shell聲明序列: #! #!/bin/bash ? 添加注釋 注釋以#開頭 ? 第二步:運行腳本 ? 給予執行權限,在命令行上指定腳本的絕對或相對路徑 ? 直接運行解釋器,將腳本作為解釋器程序的參數運行
腳本調試
bash -n /path/to/some_script 檢測腳本中的語法錯誤 bash -x /path/to/some_script 調試執行
變量
? 變量:命名的內存空間 數據存儲方式: 字符: 數值:整型,浮點型 ? 變量:變量類型 作用: 1、數據存儲格式 2、參與的運算 3、表示的數據范圍 類型: 字符 數值:整型、浮點型
編程程序語言分類
? 強類型:定義變量時必須指定類型、參與運算必須符合類型 要求;調用未聲明變量會產生錯誤 如 java,python ? 弱類型:無須指定類型,默認均為字符型;參與運算會自動 進行隱式類型轉換;變量無須事先定義可直接調用 如: bash 不支持浮點數 ? 變量命名法則: 1、不能使程序中的保留字:例如if, for; 2、只能使用數字、字母及下劃線,且不能以數字開頭 3、見名知義 4、統一命名規則:駝峰命名法
bash中變量的種類
根據變量的生效范圍等標準: 本地變量:生效范圍為當前shell進程;對當前shell之外的其它shell進程,包括當前shell的子進程均無效 環境變量:生效范圍為當前shell進程及其子進程 局部變量:生效范圍為當前shell進程中某代碼片段(通常指函數) 位置變量:$1,$2..來表示,用于讓腳本在腳本代碼中調試通過命令行傳遞給它的參數 特殊變量:$?,$0,$*,$@,$#
本地變量
? 變量賦值: 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} ? 顯示所有環境變量: export env printenv ? 刪除: unset name ? bash有許多內建的環境變量: PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1
只讀和位置變量
只讀變量:只能聲時,但不能修改和刪除 readonly name declare -r name 位置變量:在腳本代碼中調用通過命令行傳遞給腳本的參數 $1,$2..:對應第一、第二等參數,shift[n]換位置 $0:命令本身 $*:傳遞給腳本的所有參數,全部參數合為一個字符串 $@:傳遞給腳本的所有參數,每個參數為獨立字符串 $#:傳遞給腳本的參數的個數 $@,$*只在被雙引號包起來的時候才會有差異 示例:判斷給出的文件的行數 linecount="$(wc -l $1| cut -d' ' -f1)" ho "$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之間隨機數
賦值
增強型賦值: +=,-=,*=,/=,%= let varOPERvalue 例如:let count+=3 自加3后自賦值 自增,自減: let var+=1 let var++ let var-=1 let var--
邏輯運算
true,false 1 0 與: 1 與 1 = 1 1 與 0 = 0 0 與 1 = 0 0 與 0 = 0 或: 1 或 1 = 1 1 或 0 = 1 0 或 1 = 1 0 或 0 = 0 非:! ! 1 = 0 ! 0 = 1 短路運算: 短路與: 第一個為0,結果必定為0; 第一個為1,第二個必須要參與運算; 短路或: 第一個為1,結果必定為1; 第一個為0,第二個必須要參與運算; 異或: ^ 異或的兩個值,相同為假,不同為真
聚集命令
有兩種聚集命令的方法: 復合式:date;who |wc -l 命令會一個接一個地運行 子shell:(date;who |wc -l) >> /tmp/trace 所有的輸出都被發送給單個STDOUT和STDERR
退出狀態
進程使用退出狀態來報告成功或失敗 0 代表成功,1-255代表失敗 $?變量保存最近的命令退出狀態 例如: $ ping -c1 -W1 hostname &> /dev/null $ echo $?
退出狀態碼
bash自定義退出狀態碼 exit [n]:自定義退出狀態碼; 注意:腳本中一旦遇到exit命令,腳本會立即終止;終止退出狀態取決于exit命令后面的數字 注意:如果未給腳本指定退出狀態碼,整個腳本的退出狀態碼取決于腳本中執行的最后一條命令的狀態碼
條件測試
? 判斷某需求是否滿足,需要由測試機制來實現; 專用的測試表達式需要由測試命令輔助完成測試過程; ? 評估布爾聲明,以便用在條件性執行中 ? 若真,則返回0 ? 若假,則返回1 ? 測試命令: ? test EXPRESSION ? [ EXPRESSION ] ? [[ EXPRESSION ]] 注意: EXPRESSION前后必須有空白字符
條件性的執行操作符
? 根據退出狀態而定,命令可以有條件地運行 ? && 代表條件性的AND THEN ? || 代表條件性的OR ELSE ? 例如: $ 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
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的測試類型
數值測試: -eq:是否等于 -ne:是不等于否 -gt:是否大于 -ge:是否大于等于 -lt:是否小于 -le:是否小于等于
bash的測試類型
? 字符串測試: ==:是否等于; >: ascii碼是否大于ascii碼 <: 是否小于 !=: 是否不等于 =~:左側字符串是否能被右側的PATHERN所匹配 注意:此表達式一般用于[[ ]]中; -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: 是否存在且可執行 ? 文件特殊權限測試: -g FILE:是否存在且擁有sgid權限; -u FILE:是否存在且擁有suid權限; -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 非 如: [ -e FILE ] && [ -r FILE ] ? 第二種方式: EXPRESSION1 -a EXPRESSION2 并且 EXPRESSION1 -o EXPRESSION2 或者 ! EXPRESSION
使用read命令來接收輸入
使用read來把輸入值分配給一個或多個shell變量: -p 指定要顯示的提示 -t TIMEOUT 多久以后沒輸入命令自動退出 read 從標準輸入中讀取值,給每個單詞分配一個變量,所有剩余的單詞都被分配給最后一個變量 read -p “Enter a filename: “ FILE
流程控制
? 過程式編程語言: 順序執行 選擇執行 循環執行 ###條件選擇if語句 ? 選擇執行: ? 注意: if語句可嵌套 ? 單分支 if 判斷條件: then 條件為真的分支代碼 fi ? 雙分支 if 判斷條件; then 條件為真的分支代碼 else 條件為假的分支代碼 fi ? 多分支 if CONDITION1; then if-true elif CONDITION2; then if-ture elif CONDITION3; then if-ture ... else all-false fi ? 逐條件進行判斷,第一次遇為“真”條件時,執行其分支, 而后結束整個if語句 條件判斷:case語句 case 變量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 默認分支 ;; esac case支持glob風格的通配符: *: 任意長度任意字符 ?: 任意單個字符 []:指定范圍內的任意單個字符 a|b: a或b
循環
? 循環執行 將某代碼段重復運行多次 重復運行多少次: 循環次數事先已知 循環次數事先未知 有進入條件和退出條件 ? for, while, until
for循環
? for 變量名 in 列表;do 循環體 done ? 執行機制: 依次將列表中的元素賦值給“變量名” ; 每次賦值后即執行一次循環體; 直到列表中的元素耗盡,循環結束 ? 列表生成方式: (1) 直接給出列表 (2) 整數列表: (a) {start..end} (b) $(seq [start [step]] end) (3) 返回列表的命令 $(COMMAND) (4) 使用glob, 如: *.sh (5) 變量引用; $@, $*
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
創建無限循環
? while true; do 循環體 ? done ? until false; do 循環體 ? Done
特殊用法
? while循環的特殊用法(遍歷文件的每一行): while read line; do 循環體 done < /PATH/FROM/SOMEFILE ? 依次讀取/PATH/FROM/SOMEFILE文件中的每一行,且將 行賦值給變量line ? 雙小括號方法,即((…))格式,也可以用于算術運算 ? 雙小括號方法也可以使bash Shell實現C語言風格的變量操作 #I=10 #((I++)) ? for循環的特殊格式: for ((控制變量初始化;條件判斷表達式;控制變量的修正表達式)) do 循環體 done ? 控制變量初始化:僅在運行到循環代碼段時執行一次 ? 控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而后再做條件判斷
select循環與菜單
?select variable in list do 循環體命令 done ?select 循環主要用于創建菜單,按數字順序排列的 菜單項將顯示在標準錯誤上,并顯示 PS3 提示符,等待用戶輸入 ? 用戶輸入菜單列表中的某個數字,執行相應的命令 ? 用戶輸入被保存在內置變量 REPLY 中。
select與case
?select 是個無限循環,因此要記住用 break 命令退 出循環,或用 exit 命令終止腳本。也可以按 ctrl+c退出循環。 ?select 經常和 case 聯合使用 ?與 for 循環類似,可以省略 in list , 此時使用位置 參量
函數介紹
1、函數function是由若干條shell命令組成的語句塊,實現代碼重用和模塊化編程 2、它與shell程序形式上是相似的,不同的是它不是一個單獨的進程,不能獨立運行,而是shell序的一部分。 3、函數與shell程序比較相似,區別在于: ? Shell程序在子Shell中運行 ? 而Shell函數在當前Shell中運行。因此在當前Shell中,函數可以對shell中變量進行修改
定義函數
? 函數由兩部分組成:函數名和函數體。 ? 語法一: function f_name { ...函數體... }
函數使用
函數的定義和使用: 可在交互式環境下定義函數 可將函數放在腳本文件中作為它的一部分 可放在只包含函數的單獨文件中 調用:函數只有被調用才會執行; 調用:給定函數名 函數名出現的地方,會被自動替換為函數代碼 函數的生命周期:被調用時創建,返回時終止
函數返回值
函數有兩種返回值; 函數的執行結果返回值: (1)使用echo或printf命令進行輸出 (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。 文件名可任意選取,但最好與相關任務有某種聯系。例如:function.main 一旦函數文件載入shell,就可以在命令行或腳本中調用函數。可以使用set命令查看所有定義的函數,其輸出列表包括已經載入shell的所有函數。 若要改動函數,首先用unset命令從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 ? 定位函數文件并載入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命令,函數將不再顯示
函數參數
? 函數可以接受參數: 傳遞參數給函數:調用函數時,在函數名后面以空白分隔給定參數列表即可;例如“ testfunc arg1 arg2 ...” 在函數體中當中,可使用$1, $2, ...調用這些參數;還可以使用$@, $*, $#等特殊變量
函數變量
? 變量作用域: 環境變量:當前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 5
作業
1、打印九九乘法表
2、利用變量RANDOM生成10個隨機數字,輸出這個10數字,并顯示其中的最大者和最小者
3、打印國際象棋棋盤
4、寫個腳本:
*
5、掃描/etc/passwd文件每一行,如發現GECOS字段為空,則填充用戶名和單位電話為62985600,并提示該用戶的GECOS信息修改成功
6函數
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/SCRIPTNAME文件存在,則顯示“ SCRIPTNAME is running…”如果/var/lock/subsys/SCRIPT_NAME文件不存在,則顯示“ SCRIPTNAMEis stopped…”其中: SCRIPTNAME為當前腳本名
(7)在所有模式下禁止啟動該服務,可用chkconfig 和 service命令管理
1 #!/bin/bash 2 # 3 # 4 cat << EOF 5 start 6 stop 7 restart 8 status 9 ================== 10 EOF 11 12 read -p "please input options:" option 13 14 while [ "$option" != "start" -a "$option" != "stop" -a "$option" != "restart" -a "$option" != "status" ];do 15 read -p "please input options again:" option 16 done 17 18 file=/var/lock/subsys/SCRIPT_NAME 19 20 start(){ 21 if [[ -f $file ]];then 22 echo "this file has been exist" 23 else 24 touch $file 25 echo "start success" 26 fi 27 } 28 29 stop(){ 30 if [[ -f $file ]];then 31 rm -f $file 32 echo "stop success" 33 else 34 echo "this file has been disappion" 35 fi 36 } 37 38 status(){ 39 if [ -z $file ];then 40 echo "SCRIPT_NAME is running" 41 else 42 echo "SCRIPT_NAME is stopping" 43 fi 44 } 45 46 case $option in 47 start) 48 start 49 ;; 50 stop) 51 stop 52 ;; 53 restart) 54 start 55 stop 56 ;; 57 *) 58 status 59 ;; 60 esac 61 62
7、寫一個函數實現兩個數字做為參數,返回最大值
8、斐波那契數列又稱黃金分割數列,因數學家列昂納多·斐波那契以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的是這樣一個數列: 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階斐波那契數列
原創文章,作者:15152188070,如若轉載,請注明出處:http://www.www58058.com/38938