程序:算法+數據結構,數據是程序的核心javascript
算法:處理數據的方式php
數據結構:數據在計算機中的類型和組織方式html
按程序編程風格:java
過程式:以指令爲中心,數據服務於指令node
對象式:以數據爲中心,指令服務於數據python
按程序運行方式:mysql
編譯運行:高級語言-->編譯器-->機器代碼-->執行linux
源代碼須要編譯器轉換爲程序文件,運行程序文件時不須要編譯器的參與,所以程序執行效率高ios
好比:C,C++nginx
解釋運行:高級語言-->執行-->解釋器-->機器代碼
源代碼不須要事先編譯,運行時啓動解釋器然後由解釋器邊解釋邊運行,所以效率比較低
好比:shell,python,php,JavaScript,perl
按編程實現是調用庫仍是調用外部的程序文件:
非完整編程語言:利用系統上的命令及編程組件進行編程,shell腳本
完整的編程語言:利用庫和編程組件進行編程,非shell腳本
按編程模型:
面向過程編程語言
以指令爲中心來組織代碼,以過程或函數爲基礎,數據服務於代碼,圍繞指令來組織數據;這種語言對底層硬件,內存等操做比較方便,可是寫代碼和調試維護等會很麻煩。 他們按照順序執行,選擇執行,循環執行 好比:C bash C++ python
面向對象的編程語言
以數據爲中心來組織代碼,以對象做爲基本程序結構單位的程序設計語言,指令服務於數據,圍繞數據來組織指令;指用於描述的設計是以對象爲核心,而對象是程序運行時刻的基本成分。語言中提供了類、繼承等成分。 對象:特定的數據類型 類class:實例化成爲對象 好比:Java C++ python
綜上所述可知:
shell腳本編程屬於解釋運行的過程式編程語言且依賴於外部程序文件來運行
編程基本結構
各類系統命令的組合
數據存儲:變量、數組
表達式: a+b
語句: if
一種爲shell編寫的腳本程序;
是Linux命令的堆砌;
但因爲Linux中不少命令不具備冪等性,須要設定程序邏輯來判斷運行條件是否知足,以免其運行中發生錯誤
冪等性
即一個操做,不論執行多少次,產生的效果和返回的結果都是同樣的!
減小重複性的工做
自動化經常使用命令
執行系統管理和故障排除
建立簡單的應用程序
處理文本或文件
自動化安裝操做系統
kickstart 底層shell腳本
cobbler 底層shell腳本
初始化操做系統 SSH優化 關閉SElinux 防火牆放行須要的端口(80 443 22修改 10050) YUM源 時間同步 系統最大描述符 內核參數優化 字符集優化 禁止開機自動啓動 修改主機名稱 (修改公司網卡名稱)... 手動操做要注意 命令行安全 bash的命令歷史 寫入shell腳本(經常使用)
安裝服務 Nginx PHP MySQL Rsync等等... 針對不一樣的版本寫入shell腳本自動安裝
配置服務
啓動服務 全部的服務底層的啓動方式都是使用的shell腳本 公司本身研發的程序 nohup python3.5 test.py --redis --port --mysql --port -a xxxx & 複製一下 寫入腳本 sh start_test_py.sh 如何中止py程序 ps axu|grep test.py |grep -v grep|awk '{print $2}'|xargs kill -9 複製一下 寫入腳本 sh stop_test_py.sh 把py的進程的端口和PID取出來 來判斷是否運行
日誌統計 查看程序運行的狀況 統計咱們須要的數據 日誌切割 定時任務+腳本 統計數據 定時任務+腳本 ---> 經過郵件發送給管理員 ELK 日誌統計界面 py開發日誌界面 py界面----> 數據庫 <----數據 日誌展現
監控 監控服務 服務端口是否存在 服務是否存在 服務器的硬件資源使用狀況 狀態 日誌 網絡 Zabbix 經過腳本統計---> 測試---> 添加到zabbix服務 (cacti監控流量 Nagios寬帶運營商 IT公司)
腳本存放在固定的目錄/server/scripts
統一管理
腳本使用.sh
結尾,讓咱們能識別是shell腳本
腳本命名,見名知其意
腳本內的註釋最好不用中文(能夠用)
腳本內的成對的符號一次性寫完再寫內容
默認解析器: #!/usr/bin/env bash
會本身判斷使用的shell是什麼,並加載相應的環境變量
程序名,避免更改文件名爲沒法找到正確的文件
版本號
修改時間
做者相關信息
該程序的做用,及注意事項
最後是各版本的更新簡要說明
# 主函數 []<-() <-------函數註釋這樣寫
function main(){
local var="Hello World!!!"
echo ${var}
}
# info級別的日誌 []<-(msg:String) <-------帶入參的函數註釋
log_info(){
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2
}
# error級別的日誌 []<-(msg:String) <-------帶入參的函數註釋
log_error(){
# todo [error]用紅色顯示 <------函數內註釋
local msg=$1 # 將要輸出的日誌內容 <------變量的註釋緊跟在變量的後面
if [[ x"${msg}" != x"" ]];then
# 註釋 <-------函數內註釋 `#` 與縮進格式對整齊
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2
fi
}
使用兩個空格進行縮進,不使用tab縮進
不在一行的時候使用 \
進行換行,使用 \
換行的原則是整齊美觀
使用解釋器(sh或者bash)運行腳本,開啓一個子shell運行腳本內容
執行腳本絕對路徑或相對路徑,須要腳本有執行權限
使用. 或者source運行腳本,在當前父shell中運行裏面的內容
傳遞給|bash
執行,不經常使用
能夠給腳本加上執行權限
chmod +x /server/scripts/one.sh
,並將腳本的絕對路徑添加到path變量中echo PATH=/server/scripts:$PATH > /etc/profile.d/shell.sh
,使變量當即生效. /etc/profile.d/shell.sh
,就能夠像運行普通命令同樣直接執行腳本了!
[root@oldboyedu-lnb ~]# name=f;(echo $name;name=z;echo $name);echo $name # 小括號會開啓子進程,賦予的變量,只在小括號內有效,執行完命令後,就會退出子進程。
f
z
f
[root@oldboyedu-lnb ~]# name=f;{ echo $name;name=z;echo $name; };echo $name # 大括號不會開啓子進程,在當前進程有效,執行完命令後,留在當前進程。
f
z
z
# 語法檢測
bash -n /path/to/script
# 調試執行
bash -x /path/to/script
echo -e "\033[31malong\033[0m" 顯示紅色along
echo -e "\033[1;31malong\033[0m" 高亮顯示紅色along
echo -e "\033[41malong\033[0m" 顯示背景色爲紅色的along
echo -e "\033[31;5malong\033[0m" 顯示閃爍的紅色along
color=$[$[RANDOM%7]+31]
echo -ne "\033[1;${color};5m*\033[0m" 顯示閃爍的隨機色along
不能使程序中的保留字:例如if,for
只能使用數字、字母及下劃線,且不能以數字開頭
見名知義
統一命名規則:駝峯命名法
建議:
全局變量大寫
局部變量小寫
函數名小寫
變量賦值使用 =
等號,左右不能留有空格
使用變量時推薦使用 "${}"
雙引號和大括號包裹
var1="Hello World" # 正確,推薦使用雙引號
var2=6.70 # 小數
var3="${var1}" # 推薦 雙引號和大括號 包裹
單引號裏的任何字符都會原樣輸出,單引號字符串中的變量是無效的,單引號字串中不能出現單引號(對單引號使用轉義符後也不行)。 雙引號中的普通字符都會原樣輸出,可使用$引用變量,雙引號中能夠出現單引號。
常量必定要定義成readonly
函數中的變量要用local修飾,定義成局部變量,這樣在外部遇到重名的變量也不會影響
web="www.chen-shang.github.io"
function main(){
local name="chenshang" # 這裏使用local定義一個局部變量
local web="${web}" # 這裏${}內的web是全局變量,以後在函數中在使用web變量都是使用的局部變量
local web2="${web}" # 對於全局變量,雖然在使用的時候直接使用便可,但仍是推薦使用一個局部變量進行接收,而後使用局部變量,以防止在多線程操做的時候出現異常(至關於java中的靜態變量在多線程中的時候須要注意線程安全同樣,但常量除外)
}
變量一經定義,不容許刪除(也就是禁用unset命令)
強類型:
變量不通過強制轉換,它永遠是這個數據類型,不容許隱式的類型轉換。通常定義變量時必須指定類型、參與運算必須符合類型要求;調用未聲明變量會產生錯誤 如:java , c# ,python
弱類型:
語言的運行時會隱式作數據類型轉換。無須指定類型,默認均爲字符型;參與運算會自動進行隱式類型轉換;變量無須事先定義可直接調用 如:bash 不支持浮點數,php,javascript
shell中變量的基本類型就是String、數值(能夠本身看作Int、Double之類的)、Boolean。
Boolean 實際上是Int類型的變種, 在shell中0表明真、非0表明假,因此每每在shell腳本中用 readonly TURN=0 && readonly FALSE=1
。
根據變量的生效範圍等標準劃分下面變量類型:
局部變量:生效範圍爲當前shell進程;對當前shell以外的其它shell進程,包括當前shell的子shell進程均無效
環境變量:生效範圍爲當前shell進程及其子進程
本地變量:生效範圍爲當前shell進程中某代碼片段,一般指函數
位置變量:$1, $2, ...
來表示,用於讓腳本在腳本代碼中調用經過命令行傳遞給它的參數
特殊變量:$?, $0, $*, $@, $#,$$
變量賦值:name=‘value’
變量引用:${name}
或者 $name
(1) 能夠是直接字串:name=「root" (2) 變量引用:name="$USER" (3) 命令引用:name=`COMMAND` name=$(COMMAND)
" "
弱引用,其中的變量引用會被替換爲變量值
' '
強引用,其中的變量引用不會被替換爲變量值,而保持原字符串
顯示已定義的全部變量:
刪除變量:
變量聲明、賦值: export name=VALUE
declare -x name=VALUE
變量引用: $name, ${name}
顯示全部環境變量: env
print env
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, ... 對應第一、第2等參數,shift [n]換位置,從$9之後須要加{}表示總體 set -- 清空全部位置變量
$0 腳本文件名稱,若是全路徑執行則帶全路徑,可使用basename只獲取名字 $# 傳遞給腳本的參數的個數 $* 傳遞給腳本的全部參數 $@ 傳遞給腳本的全部參數 "$*" 所有參數合爲一個字符串,可在循環中驗證 "$@" 每一個參數爲獨立字符串,可在循環中驗證 $$ 運行腳本的PID $! 上一個運行腳本的PID $_ 當前命令行的最後一個參數, 相似於ESC .
$$
和$BASHPID
區別:二者都是當前進程的編號,可是$BASHPID
更精確
只輸出路徑的基名
[root@oldboyedu-lnb ~]# basename /etc/passwd passwd
0 表明成功 1-255 表明失敗 $? 保存上一條命令的退出狀態
例如:
ping -c1 -W1 hostdown &> /dev/null echo $? exit [n] 自定義退出狀態碼
注意:
腳本中一旦遇到exit命令,腳本會當即終止;終止退出狀態取決於exit命令後面的數字
若是未給腳本指定退出狀態碼,整個腳本的退出狀態碼取決於腳本中執行的最後一條命令的狀態碼
[root@shell ~]# test='I am oldboy' [root@shell ~]# url='www.baidu.com'
${var:n:x}
切片
[root@shell ~]# echo ${test:2:2} # (2,2+2]從第二個字符開始向後兩位爲止 am [root@shell ~]# echo $test|awk '{print $2}' am [root@shell ~]# echo $test|cut -c3-4 am
${#var}
字符長度
[root@shell ~]# echo ${#test} 11 [root@shell ~]# echo $test|wc -L 11 [root@shell ~]# expr length "$test" 11 [root@shell ~]# echo $test|awk '{print length}' 11 統計出字符串小於3的單詞 筆試題 I am lzhenya teacher I am 18 [root@shell ~]# cat for.sh for i in I am lzhenya teacher I am 18 do [ ${#i} -lt 3 ] && echo $i done [root@shell ~]# sh for.sh I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|xargs -n1|awk '{if(length<3)print}' I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|awk '{for(i=1;i<=NF;i++)if(length($i)<3)print $i}' I am I am 18
刪除匹配內容
支持通配符*
${var#}
從前日後匹配,${var##}
貪婪匹配
${var%}
從後往前匹配,${var%%}
貪婪匹配
若是要匹配#
或%
,需使用\
轉義
[root@shell ~]# echo ${url#www.} baidu.com [root@shell ~]# echo ${url#*.} baidu.com [root@shell ~]# echo ${url#*.*.} com [root@shell ~]# echo ${url##*.} com [root@shell ~]# echo ${url%.com} www.baidu [root@shell ~]# echo ${url%.*} www.baidu [root@shell ~]# echo ${url%.*.*} www [root@shell ~]# echo ${url%%.*} www
${var/a/b}
替換匹配內容
${var//a/b}
貪婪匹配
[root@shell ~]# echo ${url/w/W} Www.baidu.com [root@shell ~]# echo ${url//w/W} WWW.baidu.com [root@shell ~]# echo ${url/baidu/sina} www.sina.com [root@shell ~]# echo $url|sed 's#www#WWW#g' WWW.baidu.com
+, -, *, /, %取模(取餘), **(乘方)
,乘法符號有些場景中須要轉義
整數計算使用 expr
或者 $[]
或者$(())
(運算最快)或者 let
小數計算使用 bc
計算器
實現算術運算:
(1) var=$(expr arg1 arg2 arg3 ...) (2) var=$[算術表達式] (3) var=$((算術表達式)) (4) let var=算術表達式 (5) declare –i var = 數值 (6) echo ‘算術表達式’ | bc
隨機數
bash有內建的隨機數生成器變量:$RANDOM(0-32767) 生成隨機數 echo $RANDOM 生成指定範圍隨機數 示例: # 生成隨機7個數(0-6) echo $[RANDOM%7] # 生成隨機7個數(31-37) echo $[$[RANDOM%7]+31]
生成隨機字符:cat /dev/urandom # 生成8個隨機大小寫字母或數字 cat /dev/urandom |tr -dc [:alnum:] |head -c 8 tr -dc ‘a-zA-Z0-9’</dev/urandom|head -c8
加強型賦值:
+=, -=, *=, /=, %=
let var OPER value 例如:let count+=3 自加3後自賦值
自增,自減: let var+=1 let var++ let var-=1 let var--
let var=i++ 是賦值後加 let var=++i 是先加後賦值
# 取1-63的餘,其中 RANDOM%63 的值是0-62,加1就是1-63 echo $[RANDOM%63+1] # 生成隨機顏色 echo -e "\033[1;$[RANDOM%7+31]m 字符串\033[0m"
邏輯運算
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 ! true
! 0 = 1 ! false
短路與 &&
第一個爲0,結果一定爲0
第一個爲1,第二個必需要參與運算
短路或 ||
第一個爲1,結果一定爲1
第一個爲0,第二個必需要參與運算
異或:^ 異或的兩個值,相同爲假,不一樣爲真
短路與和短路或
[ $RANDOM%6 –eq 0 ] && rm –rf /* || echo 「click」
# 數字互換
A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B
A=01010=10
B=10100=20
A=$[A^B]=11110=30
A=11110=30
B=10100=20=10
B=$[A^B]=01010
A=11110=30
B=01010=10
A=$[A^B]=10100=20
[root@oldboyedu-lnb ~]# A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B
A=20 B=10
非特別說明,則全部文件類操做都會追蹤到軟連接的源文件
test EXPRESSION
[ EXPRESSION ]
(( EXPRESSION )) 算術表達式
[[ EXPRESSION ]] 不會發生文件名擴展或者單詞分割,會發生參數擴展和命令替換
注意:EXPRESSION 先後必須有空白字符
test -d "$HOME" ;echo $? [ "abc" != "def" ];echo $? test EXPRESSION && echo "exist" || echo "not exist" 更人性化地顯示結果 test EXPRESSION && echo true || echo false 更人性化地顯示結果 test -e file && echo "exist" || echo "not exist" test 3 -gt 4 && echo true || echo false
bash的數值測試
-v VAR 變量VAR是否設置 數值測試: -gt 是否大於 -ge 是否大於等於 -eq 是否等於 -ne 是否不等於 -lt 是否小於 -le 是否小於等於
bash的字符串測試
= 是否等於 > ascii碼是否大於ascii碼 < 是否小於 != 是否不等於 =~ 左側字符串是否可以被右側的 正則表達式 所匹配 注意: 此表達式通常用於[[ ]]中,[[ ]]中匹配正則表達式或通配符,不須要引號 [[ hello == hell? ]] && echo true || echo false [[ 2\<3 ]] && echo true || echo false [[ 0 < 1 ]] && echo true || echo false [[ 2 -lt 3 ]] && echo true || echo false [ 2 \< 3 ] && echo true || echo false [ 1 = 1 ] && echo true || echo false -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:是否存在且爲套接字文件
bash的文件權限測試
文件權限測試: -r FILE:是否存在且可讀 -w FILE: 是否存在且可寫 -x FILE: 是否存在且可執行 文件特殊權限測試: -u FILE:是否存在且擁有suid權限 -g FILE:是否存在且擁有sgid權限 -k FILE:是否存在且擁有sticky權限
bash的文件屬性測試
文件大小測試: -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 而且,只能在test或[]中使用 EXPRESSION1 -o EXPRESSION2 或者,只能在test或[]中使用 ! EXPRESSION 非 第二種方式: COMMAND1 && COMMAND2 而且,短路與,表明條件性的AND THEN,只能在[[]]中使用 COMMAND1 || COMMAND2 或者,短路或,表明條件性的OR ELSE,只能在[[]]中使用 ! COMMAND 非 如:[ -f 「$FILE」 ] && [[ 「$FILE」=~ .*\.sh$ ]]
條件性的執行操做符
示例:
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 "$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 # 判斷字符串爲空或者localhost.localdomain,則臨時修改主機名爲 www.magedu.com [ -z 「$HOSTNAME」 -o $HOSTNAME "=="localhost.localdomain" ] && hostname www.magedu.com
read 把輸入值分配給一個或多個shell變量 -a 後跟一個變量,該變量會被認爲是個數組,而後給其賦值,默認是以空格爲分割符 -e 在輸入的時候可使用命令補全功能 -r 屏蔽\的轉義功能 -u 後面跟fd,從文件描述符中讀入,該文件描述符能夠是exec新開啓的 -p 指定輸入前打印提示信息 -s 靜默輸入,輸入的字符不在屏幕上顯示,通常用於密碼 -n N 指定輸入的字符長度最大爲N -d ‘字符’ 以輸入的指定‘字符’做爲結束符 -t N TIMEOUT爲N秒,超時退出 read 從標準輸入中讀取值,給每一個單詞分配一個變量,全部剩餘單詞都被分配給最後一個變量 read -p 「Enter a filename: 「 FILE 示例: 修改主機名稱爲shell,而且修改eth0網卡IP地址爲88 [root@shell ~]# cat hostname.sh #!/bin/bash eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0' old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'` read -p "please input hostname: " name read -p "please input New IP: " IP hostnamectl set-hostname $name sed -i "s#$old_ip#$IP#g" $eth0_cfg grep $IP $eth0_cfg [root@shell ~]# cat ping.sh read -p "Please Input URL: " url ping -c2 -W1 $url &>/dev/null [ $? -ne 0 ] && echo "ping不通" || echo "通了"
cat <<EOF
、cat <<-EOF
的區別
用於執行腳本的時候,須要往一個文件裏自動輸入N行內容。
cat用於顯示文本文件內容,所有輸出。
EOF是END Of File的縮寫,表示自定義終止符,Ctrl-D就表明EOF。
man說明:
If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter.
翻譯:
若是重定向的操做符是<<-,那麼分界符(EOF)所在行的開頭部分的製表符(Tab)都將被去除。
也就是說:
cat <<EOF
中EOF必須頂行寫,前面不能用製表符或者空格。若是結束分解符EOF前有製表符或者空格,則EOF不會被當作結束分界符,只會繼續被當作stdin來輸入。
cat <<-EOF
中就算最後的EOF前面有多個製表符和空格,但仍然會被當作結束分界符,表示stdin的結束。
trap命令用於指定在接收到信號後將要採起的動做,常見的用途是在腳本程序被中斷時完成清理工做。當shell接收到sigspec指定的信號時,arg參數(命令)將會被讀取,並被執行,而不會執行原操做。
trap [-lp] [[arg] sigspec ...] -l 讓shell打印一個命令名稱和其相對應的編號的列表 -p 若是有-p選項而沒有提供arg參數,則會打印全部與sigspec指定信號相關聯的的trap命令; 若是沒有提供任何參數或者僅有-p選項,trap命令將會打印與每個信號有關聯的命令的列表; [arg]參數缺省或者爲「-」,每一個接收到的sigspec信號都將會被重置爲它們進入shell時的值 [arg]參數是空字符串每個由sigspec指定的信號都會被shell和它所調用的命令忽略;
trap commands signals # commands 能夠是任何有效的Linux命令,或一個用戶定義的函數, # signals 能夠是任意數量的信號,或你想來捕獲的列表。 # 信號有3種表達方法:信號的數字二、全名SIGINT、縮寫INT
參考實例:
trap "rm -f $WORKDIR/work1$ $WORKDIR/dataout$; exit" 1 2 收到指定信號後清理臨時文件 trap '' 1 2 20 收到指定信號後忽略信號 trap '-' 1 2 20 trap 1 2 20 恢復信號的默認操做,重設陷阱
每一個sigspec信號都是是以名字或者編號的形式定義在signal.h頭文件中,信號的名字是不區分大小寫的,其前綴SIG是可選的,有如下狀況:
若是sigspec是EXIT(0),那麼arg指定的命令將會在shell上執行退出命令時執行
若是sigspec是DEBUG,那麼arg指定的命令將會在如下每一個命令執行以前執行:
簡單命令,for語句,case語句,select命令,算法命令,在函數內的第一條命令。
若是sigspec是ERR,那麼arg指定的命令將會在任何簡單命名執行完後返回值爲非零值時執行,可是也有如下例外狀況,arg命令不會執行,這些規則一樣適用於errexit選項:
若是執行失敗的命令是緊跟在while或者until關鍵字以後的一組命令中的一部分時
若是執行失敗的命令是if測試語句的一部分時,是 && 和 ||鏈接的列表中的一部分時
若是執行失敗的命令的返回值是被取反過的(經過!操做符)
若是sigspec是RETURN,那麼arg指定的命令在每次shell函數或者腳本用"."或者內置的命令執行完成後執行
注意:
在shell入口處被忽略的命令是無法被trap和reset的。
被trap的信號,在建立的子進程中使用時會在子進程被建立時被重置爲原始的值。
若是trap使用的sigspec信號是無效的信號,則trap命令返回false(失敗),不然返回true(成功)。
① 打印0-9,ctrl+c不能終止
執行腳本後,打印0-9,每秒一個數字,ctrl+c轉換爲echo press ctrl+c
#!/bin/bash trap 'echo press ctrl+c' 2 for ((i=0;i<10;i++));do sleep 1 echo $i done
② 打印0-3,ctrl+c不能終止,3以後恢復,能終止
執行腳本後,打印0-3,每秒一個數字,ctrl+c不能終止,打印3以後解除捕獲2信號,能終止
#!/bin/bash trap '' 2 trap -p for ((i=0;i<3;i++));do sleep 1 echo $i done trap '-' SIGINT for ((i=3;i<10;i++));do sleep 1 echo $i done
信號
信號是一種進程間通訊機制,它給應用程序提供一種異步的軟件中斷,使應用程序有機會接受其餘程序活終端發送的命令(即信號)。
應用程序收到信號後,有三種處理方式:忽略,默認,或捕捉。
進程收到一個信號後,會檢查對該信號的處理機制:
若是是SIG_IGN,就忽略該信號;
若是是SIG_DFT,則會採用系統默認的處理動做,一般是終止進程或忽略該信號;
若是給該信號指定了一個處理函數(捕捉),則會中斷當前進程正在執行的任務,轉而去執行該信號的處理函數,返回後再繼續執行被中斷的任務。
在有些狀況下,咱們不但願本身的shell腳本在運行時刻被中斷,好比說咱們寫得shell腳本設爲某一用戶的默認shell,使這一用戶進入系統後只能做某一項工做,如數據庫備份,咱們不但願用戶使用Ctrl+c之類可以進入到shell狀態,作咱們不但願作的事情。這便用到了信號處理。
常見信號:
1) SIGHUP: 無須關閉進程而讓其重讀配置文件
2) SIGINT: 停止正在運行的進程;至關於Ctrl+c
3) SIGQUIT: 至關於ctrl+\
9) SIGKILL: 強制殺死正在運行的進程;本信號不能被阻塞,處理和忽略。
15) SIGTERM :終止正在運行的進程(默認爲15)
18) SIGCONT :繼續運行
19) SIGSTOP :後臺休眠
信號名稱 | 信號數 | 描述 |
---|---|---|
SIGHUP | 1 | 本信號在用戶終端鏈接(正常或非正常)結束時發出, 一般是在終端的控制進程結束時, 通知同一session內的各個做業, 這時它們與控制終端再也不關聯。登陸Linux時,系統會分配給登陸用戶一個終端(Session)。在這個終端運行的全部程序,包括前臺進程組和後臺進程組,通常都屬於這個Session。當用戶退出Linux登陸時,前臺進程組和後臺有對終端輸出的進程將會收到SIGHUP信號。這個信號的默認操做爲終止進程,所以前臺進程組和後臺有終端輸出的進程就會停止。對於與終端脫離關係的守護進程,這個信號用於通知它從新讀取配置文件。 |
SIGINT | 2 | 程序終止(interrupt)信號, 在用戶鍵入INTR字符(一般是Ctrl+C)時發出。 |
SIGQUIT | 3 | 和SIGINT相似, 但由QUIT字符(一般是Ctrl+/)來控制。進程在因收到SIGQUIT退出時會產生core文件,在這個意義上相似於一個程序錯誤信號。 |
SIGFPE | 8 | 在發生致命的算術運算錯誤時發出。不只包括浮點運算錯誤,還包括溢出及除數爲0等其它全部的算術的錯誤。 |
SIGKILL | 9 | 用來當即結束程序的運行。本信號不能被阻塞,處理和忽略。 |
SIGALRM | 14 | 時鐘定時信號,計算的是實際的時間或時鐘時間。 alarm函數使用該信號。 |
SIGTERM | 15 | 程序結束(terminate)信號,與SIGKILL不一樣的是該信號能夠被阻塞和處理,一般用來要求程序本身正常退出。shell命令kill缺省產生這個信號。 |
SIGHUP 1 /* Hangup (POSIX). */ 終止進程 終端線路掛斷 SIGINT 2 /* Interrupt (ANSI). */ 終止進程 中斷進程 Ctrl+C SIGQUIT 3 /* Quit (POSIX). */ 創建CORE文件終止進程,而且生成core文件 Ctrl+ SIGILL 4 /* Illegal instruction (ANSI). */ 創建CORE文件,非法指令 SIGTRAP 5 /* Trace trap (POSIX). */ 創建CORE文件,跟蹤自陷 SIGABRT 6 /* Abort (ANSI). */ SIGIOT 6 /* IOT trap (4.2 BSD). */ 創建CORE文件,執行I/O自陷 SIGBUS 7 /* BUS error (4.2 BSD). */ 創建CORE文件,總線錯誤 SIGFPE 8 /* Floating-point exception (ANSI). */ 創建CORE文件,浮點異常 SIGKILL 9 /* Kill, unblockable (POSIX). */ 終止進程 殺死進程 SIGUSR1 10 /* User-defined signal 1 (POSIX). */ 終止進程 用戶定義信號1 SIGSEGV 11 /* Segmentation violation (ANSI). */ 創建CORE文件,段非法錯誤 SIGUSR2 12 /* User-defined signal 2 (POSIX). */ 終止進程 用戶定義信號2 SIGPIPE 13 /* Broken pipe (POSIX). */ 終止進程 向一個沒有讀進程的管道寫數據 SIGALARM 14 /* Alarm clock (POSIX). */ 終止進程 計時器到時 SIGTERM 15 /* Termination (ANSI). */ 終止進程 軟件終止信號 SIGSTKFLT 16 /* Stack fault. */ SIGCHLD 17 /* Child status has changed (POSIX). */ 忽略信號 當子進程中止或退出時通知父進程 SIGCONT 18 /* Continue (POSIX). */ 忽略信號 繼續執行一箇中止的進程 SIGSTOP 19 /* Stop, unblockable (POSIX). */ 中止進程 非終端來的中止信號 SIGTSTP 20 /* Keyboard stop (POSIX). */ 中止進程 終端來的中止信號 Ctrl+Z SIGTTIN 21 /* Background read from tty (POSIX). */ 中止進程 後臺進程讀終端 SIGTTOU 22 /* Background write to tty (POSIX). */ 中止進程 後臺進程寫終端 SIGURG 23 /* Urgent condition on socket (4.2 BSD).*/ 忽略信號 I/O緊急信號 SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ 終止進程 CPU時限超時 SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ 終止進程 文件長度過長 SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ 終止進程 虛擬計時器到時 SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ 終止進程 統計分佈圖用計時器到時 SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ 忽略信號 窗口大小發生變化 SIGIO 29 /* I/O now possible (4.2 BSD). */ 忽略信號 描述符上能夠進行I/O SIGPWR 30 /* Power failure restart (System V). */ SIGSYS 31 /* Bad system call. */
安裝
yum install expect -y
expect [選項] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ] -c:從命令行執行expect腳本,默認expect是交互地執行的 -d:能夠輸出輸出調試信息 示例: expect -c 'expect "\n" {send "pressed enter\n"} expect -d ssh.exp
expect中相關命令 :
spawn:啓動新的進程 send:用於向進程發送字符串 expect:從進程接收字符串 interact:容許用戶交互,並停留在遠程鏈接的主機上 exp_continue:匹配多個字符串在執行動做後加此命令
expect最經常使用的語法(tcl語言:模式-動做)
單一分支模式語法:
匹配到hi後,會輸出「you said hi」,並換行
expect 「hi」 {send 「You said hi\n"}
多分支模式語法:
匹配hi,hello,bye任意字符串時,執行相應輸出。
expect "hi" { send "You said hi\n" } \ "hehe" { send "Hehe yourself\n" } \ "bye" { send "Good bye\n" }
等同以下:
expect { "hi" { send "You said hi\n"} "hehe" { send "Hehe yourself\n"} "bye" { send " Good bye\n"} }
① 用戶名密碼自動登陸系統
#!/usr/bin/expect set ip 192.168.7.100 set user root set password centos set timeout 10 # 登陸 調用user和ip兩個變量的值 spawn ssh $user@$ip expect { # 有發現yes/no 輸入 yes\n "yes/no" { send "yes\n";exp_continue } # 有發現password輸入$password的值 "password" { send "$password\n" } } # 容許用戶交互 interact
② shell調用expect腳本
#!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF # 開啓expect命令多行重定向 set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } expect "]#" { send "echo centos |passwd --stdin hehe\n" } expect "]#" { send "exit\n" } expect eof # 結束語 EOF
③ 多主機批量操縱:根據相同用戶名和密碼,批量建立用戶
一、建立IP地址清單
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 192.168.7.102 192.168.7.103 EOF
二、經過while實現批量讀取文件內容
#!/bin/bash while read ip;do user=root password=centos expect <<EOF set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } # 遠程ssh登陸後建立用戶名 expect "]#" { send "echo centos |passwd --stdin hehe\n" } # 設置密碼 expect "]#" { send "exit\n" } expect eof EOF done < iplist.txt
④ 多主機批量操縱:根據不一樣用戶名和密碼傳遞公鑰,實現免密鑰登陸
一、建立IP地址,密碼清單
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 wangwang 192.168.7.102 centos 192.168.7.103 hahahaha EOF
二、經過while實現批量讀取文件內容
#!/bin/bash ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa while read ip password;do user=root set timeout 10 expect << EOF spawn ssh-copy-id -i /root/.ssh/id_rsa.pub $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect eof EOF done <iplist.txt
順序執行
選擇執行
循環執行
選擇執行:可嵌套
單分支
if 判斷條件;then
條件爲真的分支代碼
fi
雙分支
if 判斷條件; then
條件爲真的分支代碼
else
條件爲假的分支代碼
fi
多分支
if 判斷條件1; then
條件1爲真的分支代碼
elif 判斷條件2; then
條件2爲真的分支代碼
elif 判斷條件3; then
條件3爲真的分支代碼
else
以上條件都爲假的分支代碼
fi
逐條件進行判斷,第一次遇爲「真」條件時,執行其分支,然後結束整個if語句
Example:
根據命令的退出狀態來執行命令
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
① 判斷年紀
請輸入年紀,先判斷輸入的是否含有除數字之外的字符,有,輸出"please input a int";沒有,繼續判斷是否小於150,是否大於18。
#!/bin/bash read -p "Please input your age: " age if [[ $age =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $age -ge 150 ];then echo "your age is wrong" exit 20 elif [ $age -gt 18 ];then echo "good good work,day day up" else echo "good good study,day day up" fi
② 判斷分數
請輸入成績,先判斷輸入的是否含有除數字之外的字符,有,輸出"please input a int";沒有,繼續判斷是否大於100,是否大於85,是否大於60。
#!/bin/bash read -p "Please input your score: " score if [[ $score =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $score -gt 100 ];then echo "Your score is wrong" exit 20 elif [ $score -ge 85 ];then echo "Your score is very good" elif [ $score -ge 60 ];then echo "Your score is soso" else echo "You are loser" fi
case 變量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; *) 默認分支 ;; esac case支持glob風格的通配符: *: 任意長度任意字符 ?: 任意單個字符 []: 指定範圍內的任意單個字符 a|b: a或b
① 判斷yes or no
請輸入yes or no,回答Y/y、yes各類大小寫組合爲yes;回答N/n、No各類大小寫組合爲no。
#!/bin/bash read -p "Please input yes or no: " anw case $anw in [yY][eE][sS]|[yY]) echo yes ;; [nN][oO]|[nN]) echo no ;; *) echo false ;; esac
for (( i = 0; i < 10; i++ )); do 循環體 done for item in 列表; do 循環體 done
執行機制:依次將列表中的元素賦值給「變量名」; 每次賦值後即執行一次循環體; 直到列表中的元素耗盡,循環結束
列表:
支持glob通配符,如:{1..10}
、*.sh
;
也能夠引用變量 ${array}
,如:seq 1 $1
;
① 求(1+2+...+n)的總和
sum初始值爲0,請輸入一個數,判斷輸入的值是否以1-9開頭,後面跟任意個0-9的數字,不是,就報錯;是,進入for循環,i的範圍爲1~輸入的數,每次的循環爲sum=sum+i,循環結束,最後輸出sum的值。
#!/bin/bash sum=0 read -p "Please input a positive integer: " num if [[ ! $num =~ ^[1-9][0-9]* ]] ;then echo "input error" else for i in `seq 1 $num` ;do sum=$[$sum+$i] done echo $sum fi
while [[ 循環控制條件 ]]; do 循環體 done while read -r item ;do 循環體 done < 'file_name' cat 'file_name' | while read line; do 循環體 done
循環控制條件;進入循環以前,先作一次判斷;每一次循環以後會再次作判斷;條件爲「true」 ,則執行一次循環;直到條件測試狀態爲「false」 終止循環
遍歷文件的每一行:依次讀取file_name文件中的每一行,且將行賦值給變量line
① 100之內全部正奇數之和
sum初始值爲0,i的初始值爲1;當i<=100時,進入循環,判斷 i÷2取餘,不爲0時爲奇數,sum=sum+i,i+1;爲0時,i+1;循環結束,最後輸出sum的值。
#!/bin/bash sum=0 i=1 while [ $i -le 100 ] ;do if [ $[$i%2] -ne 0 ];then let sum+=i let i++ else let i++ fi done echo "sum is $sum"
② 菜單
#!/bin/bash cat << EOF 1)gongbaojiding 2)kaoya 3)fotiaoqiang 4)haishen 5)baoyu 6)quit EOF while read -p "please choose the number: " num;do case $num in 1) echo "gongbaojiding price is 30" ;; 2) echo "kaoya price price is 80" ;; 3) echo "fotiaoqiang price is 200" ;; 4) echo "haishen price is \$20" ;; 5) echo "baoyu price is \$10" ;; 6) break ;; *) echo "please input again" esac done
③ 統計日誌訪問IP狀況
#!/bin/bash # 其中access_log爲訪問日誌,統計訪問IP和次數,導出到文件iplist.txt中 sed -rn 's/^([^[:space:]]+).*/\1/p' access_log |sort |uniq -c > iplist.txt # while read 逐行處理 while read count ip;do if [ $count -gt 100 ];then # 將統計後的日誌導出到文件crack.log中 echo from $ip access $count >> crack.log fi # while read 支持重定向,能夠將要統計的文件導入到循環中 done < iplist.txt
④ 統計磁盤使用率大於指定值的信息
#!/bin/bash # 定義報警的磁盤使用率 WARNING=10 df | awk '{if($5>$WARNING)print $0}'
#!/bin/bash # 定義報警的磁盤使用率 WARNING=10 df |sed -rn '/^\/dev\/sd/s#^([^[:space:]]+).* ([[:digit:]]+)%.*$#\1 \2#p' | while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done
#!/bin/bash # 定義報警的磁盤使用率 WARNING=10 df |awk -F"[[:space:]]+|%" '/dev\/sd/{print $1,$(NF-2)}' > disk.txt while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done < disk.txt
until [[ 循環控制條件 ]]; do 循環體 done
進入條件:循環條件爲false ;
退出條件:循環條件爲true;
恰好和while相反,因此不經常使用,用while就行。
① 監控test用戶,登陸就殺死
#!/bin/bash # 發現test用戶登陸,條件爲true,退出循環 until pgrep -u test &> /dev/null ;do # 每隔0.5秒掃描 sleep 0.5 done # 殺死test用戶相關進程 pkill -9 -u test
select variable in list do 循環體 done
① select 循環主要用於建立菜單,按數字順序排列的示菜單項將顯示在標準錯誤上,並顯示PS3提示符,等待用戶輸入
② 用戶輸入菜單列表中的某個數字,執行相應的命令
③ 用戶輸入被保存在內置變量 REPLY 中
④ select 是個無限循環,所以要記住用 break 命令退出循環,或用 exit 命令終止腳本。也能夠按 ctrl+c退出循環
⑤ select 常常和 case 聯合使用
⑥ 與for循環相似,能夠省略 in list, 此時使用位置參量
示例: 生成菜單,並顯示選中的價錢
#!/bin/bash PS3="Please choose the menu: " select menu in mifan huimian jiaozi babaozhou quit do case $REPLY in 1|4) echo "the price is 15" ;; 2|3) echo "the price is 20" ;; 5) break ;; *) echo "no the option" esac done
注意:PS3是select的提示符,自動生成菜單,選擇5退出循環。
continue [N]:提早結束第N層的本輪循環,而直接進入下一輪判斷;最內層爲第1層
break [N]:提早結束第N層循環,最內側爲第1層
例:
while CONDTITON1; do CMD1 if CONDITION2; then continue / break fi CMD2 done
(2)案例:
① 求(1+3+...+49+53+...+100)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && continue [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:作1+3+...+99的循環,當i=51時,跳過此次循環,可是繼續整個循環,結果爲:sum=2449
② 求(1+3+...+49)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && break [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:作1+3+...+99的循環,當i=51時,跳出整個循環,結果爲:sum=625
shift n 用於將參數列表list左移指定次數,最左端的那個參數就從列表中刪除,其後邊的參數繼續進入循環,n是整數 1.依次讀取輸入的參數並打印參數個數: $ cat run.sh #!/bin/bash while [ $# != 0 ];do echo "第一個參數爲:$1,參數個數爲:$#" shift done $ sh run.sh a b c d e f 第一個參數爲:a,參數個數爲:6 第一個參數爲:b,參數個數爲:5 第一個參數爲:c,參數個數爲:4 第一個參數爲:d,參數個數爲:3 第一個參數爲:e,參數個數爲:2 第一個參數爲:f,參數個數爲:1 2.把參數進行左移3個: $ cat t.sh #!/bin/bash echo -e "./t.sh arg1 arg2 arg3 arg4 arg5 arg6" str1="${1},${2},${3}" echo "str1=$str1" shift 3 str2=$@ echo "str2=$str2" $ sh t.sh 1 2 3 4 5 6 7 str1=1,2,3 3.將參數從左到右逐個移動: $ cat shift.sh #!/bin/bash while [ $# -ne 0 ] do echo "第一個參數爲: $1 參數個數爲: $#" shift done $ sh shift.sh Lily Lucy Jake Mike 第一個參數爲: Lily 參數個數爲: 4 第一個參數爲: Lucy 參數個數爲: 3 第一個參數爲: Jake 參數個數爲: 2 第一個參數爲: Mike 參數個數爲: 1
① 建立指定的多個用戶
#!/bin/bash if [ $# -eq 0 ] ;then echo "Please input a arg(eg:`basename $0` arg1)" exit 1 else while [ -n "$1" ];do useradd $1 &> /dev/null echo "User:$1 is created" shift done fi
分析:若是沒有輸入參數(參數的總數爲0),提示錯誤並退出;反之,進入循環;若第一個參數不爲空字符,則建立以第一個參數爲名的用戶,並移除第一個參數,將緊跟的參數左移做爲第一個參數,直到沒有第一個參數,退出。
② 打印直角三角形的字符
[root@oldboyedu-lnb ~]# sh trian.sh 1 2 3
1 2 3
2 3
3
true
永遠成功
false
永遠錯誤
無限循環
while true ;do 循環體 done # 或者 until false ;do 循環體 done
每次循環將循環體放入後臺執行. 繼續下一次循環. 最後等待全部線程執行完畢再退出腳本
for name in 列表 ;do { 循環體 }& done wait
① 搜尋指定ip(子網掩碼爲24的)的網段中,UP的ip地址
read -p "Please input network (eg:192.168.0.0): " net echo $net |egrep -o "\<(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>" [ $? -eq 0 ] || ( echo "input error";exit 10 ) IP=`echo $net |egrep -o "^([0-9]{1,3}\.){3}"` for i in {1..254};do { ping -c 1 -w 1 $IP$i &> /dev/null && \ echo "$IP$i is up" }& done wait
分析:請輸入一個IP地址,例如192.168.37.234,若是格式不是0.0.0.0 則報錯退出;正確則進入循環,IP變量的值爲192.168.37. i的範圍爲1-254,並行ping 192.168.37.1-154,ping通就輸出此IP爲UP。
函數遞歸: 函數直接或間接調用自身 注意遞歸層數 遞歸實例: 階乘是基斯頓·卡曼於 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
把命令行分紅單個命令詞
展開別名
展開大括號的聲明({})
展開波浪符聲明(~)
命令替換$()和``
再次把命令行分紅命令詞
展開文件通配(*、?、[abc]等等)
準備I/0重導向(<、>)
運行命令
反斜線\
會使隨後的一個字符按原意解釋
單引號'
防止全部擴展
雙引號"
除了如下狀況,防止全部擴展:
$ (美圓符號) 變量擴展(注意:"$" 輸出 $,仍有特殊含義) ` (反引號) 命令替換 \ (反斜線) 禁止單個字符擴展 ! (歎號) 歷史命令替換
HEAD_KEYWORD parameters; BODY_BEGIN BODY_COMMANDS BODY_END
將HEAD_KEYWORD和初始化命令或者參數放在第一行;
將BODY_BEGIN一樣放在第一行;
複合命令中的BODY_COMMANDS部分以2個空格縮進;
BODY_END部分獨立一行放在最後;
if
if [[ condition ]]; then # statements fi if [[ condition ]]; then # statements else # statements fi if [[ condition ]]; then # statements elif [[ condition ]]; then # statements else # statements fi
if 後面的判斷 使用 雙中括號[[]]
if [[ condition ]]; then
寫在一行
while
while [[ condition ]]; do # statements done while read -r item ;do # statements done < 'file_name'
until
until [[ condition ]]; do # statements done
for
for (( i = 0; i < 10; i++ )); do # statements done for item in ${array}; do # statements done
case
case $var in pattern ) #statements ;; *) #statements ;; esac
函數function是由若干條shell命令組成的語句塊,實現代碼重用和模塊化編程。
它與shell程序形式上是類似的,不一樣的是它不是一個單獨的進程,不能獨立運 行,而是shell程序的一部分,定義函數只對當前的會話窗口有效,若是再打開一個窗口再定義另一個函數,就對另外一個窗口有效,二者互不影響。
函數和shell程序比較類似,區別在於如下兩種:
(1)Shell程序在子Shell中運行。
(2)而Shell函數在當前Shell中運行。所以在當前Shell中,函數能夠對shell中變量進行修改。
function main(){ #函數執行的操做 #函數的返回結果 }
或
main(){ #函數執行的操做 #函數的返回結果 }
或
function main { #函數執行的操做 #函數的返回結果 }
使用關鍵字 function
顯示定義的函數爲 public 的函數,能夠供外部腳本以 sh 腳本 函數 函數入參
的形式調用
未使用關鍵字 function
顯示定義的函數爲 privat 的函數, 僅供本腳本內部調用,注意這種privat是人爲規定的,並非shell的語法,不推薦以 sh 腳本 函數 函數入參
的形式調用,注意是不推薦而不是不能。
本shell規約這樣作的目的就在於使腳本具備必定的封裝性,看到
function
修飾的就知道這個函數能被外部調用, 沒有被修飾的函數就僅供內部調用。你就知道若是你修改了改函數的影響範圍. 若是是被function修飾的函數, 修改後可能影響到外部調用他的腳本, 而修改未被function修飾的函數的時候,僅僅影響本文件中其餘函數。
如 core.sh 腳本內容以下是
# 從新設置DNS地址 []<-() function set_name_server(){ > /etc/resolv.conf echo nameserver 114.114.114.114 >> /etc/resolv.conf echo nameserver 8.8.8.8 >> /etc/resolv.conf cat /etc/resolv.conf } # info級別的日誌 []<-(msg:String) log_info(){ echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[32m [info] \033[0m $*" >&2 } # error級別的日誌 []<-(msg:String) log_error(){ # todo [error]用紅色顯示 echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[31m [error] \033[0m $*" >&2 }
則我可使用 sh core.sh set_name_server
的形式調用 set_name_server
函數,但就不推薦使用 sh core.sh log_info "Hello World"
的形式使用 log_info
和 log_error
函數,注意是不推薦不是不能。
(1)可在交互式環境下定義函數
(2)可將函數放在腳本文件中做爲它的一部分
#!/bin/bash # 定義function func_os_version,在大括號裏邊定義命令,取出操做系統的版本號,相似於定義別名同樣 func_os_version () { sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } # 直接寫上函數名,或者用echo加反引號輸出結果 echo OS version is `func_os_version`
若是命令過多,不太方便
(3)可放在只包含函數的單獨文件中
而後將函數文件載入shell
文件名可任意選取,但最好與相關任務有某種聯繫。例如:functions.main
一旦函數文件載入shell,就能夠在命令行或腳本中調用函數。可使用set
查看全部定義的函數,其輸出列表包括已經載入shell的全部函數
若要改動函數,首先用unset function_name
從shell中刪除函數。改動完畢後,再從新載入此文件
# 將定義的函數放到functions文件中
[root@centos-7 ~]# cat functions
func_os_version () {
sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release
}
[root@centos-7 ~]# cat osversion.sh
可使用declare -F
查看全部定義的函數
使子進程也可以使用
(1)聲明:export -f function_name
(2)查看:export -f
或 declare -xf
調用:給定函數名
函數名出現的地方,會被自動替換爲函數代碼
函數的生命週期:被調用時建立,返回時終止
使用腳本單獨調用函數中的某個函數
#!/usr/bin/env bash # shellcheck disable=SC1091,SC2155 readonly local TRUE=0 && readonly local FALSE=1 # 腳本使用幫助文檔 manual(){ cat "$0"|grep -v "less \"\$0\"" \ |grep -B1 "function " \ |grep -v "\\--" \ |sed "s/function //g" \ |sed "s/(){//g" \ |sed "s/#//g" \ |sed 'N;s/\n/ /' \ |column -t \ |awk '{print $1,$3,$2}' \ |column -t } ###################################################################### # 主函數 main(){ (manual) } ###################################################################### # 執行函數 [Any]<-(function_name:String,function_parameters:List<Any>) execute(){ function_name=$1 shift # 參數列表以空格爲分割左移一位,至關於丟棄掉第一個參數 function_parameters=$* (${function_name} "${function_parameters}") } case $1 in "-h" | "--help" | "?") (manual);; "") (main) ;; *) (execute "$@") ;; esac
使用如上的框架,只須要在 兩個 ######################################################################
之間寫函數,就可使用 sh 腳本名稱 腳本中的某個函數 腳本中的某個函數的入參
的形式調用函數了。 使用 sh 腳本名稱 ?
或者 sh 腳本名稱 -h/--help
就能夠查看這個腳本中的函數說明了。
在函數內部首先使用有意義的變量名接受參數,而後在使用這些變量進行操做,禁止直接操做$1
,$2
等,除非這些變量只用一次
函數的註釋 函數類型的概念是從函數編程語言中的概念偷過來的,shell函數的函數類型指的是函數的輸入到函數的輸入的映射關係
# 主函數 []<-() <-------函數註釋這樣寫 function main(){ local var="Hello World!!!" echo ${var} } # info級別的日誌 []<-(msg:String) <-------帶入參的函數註釋 log_info(){ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2 }
main函數的函數類型是 []<-() , <- 左側表的是函數的返回值類型用[]包裹, 右側是函數的參數類型用()包裹,多個參數用 ',' 分隔, 參數的描述是從 Scala 語言中偷過來, 先是參數名稱, 而後是參數類型, 中間用:分隔
對於main函數的註釋來講, #
頂格寫,後面緊跟一個空格,其實這樣寫是遵循的markdown的語法, 後面再跟一個空格,而後是 []<-(),表明這個函數沒有入參也沒有返回值,這個函數的目的就是執行這個這個函數中的命令,但我不關心這個函數的返回值。也就是利用函數的反作用來完成咱們想要的操做。
對於log_info也是同樣不過最後的函數類型是 []<-(msg:String) 表明入參是一個string類型的信息,而後也沒有返回值。 關於函數的返回值,我理解的函數的返回值有兩種形式,一種是顯示的return一種是隱式的echo
如下是幾種常見的寫法
[]<-() [String]<-(var1:String,var2:String) [Boolean]<-(var1:String,var2:Int) []<-(var1:String)
一、函數的執行結果返回值:
(1) 使用echo等命令進行輸出
(2) 函數體中調用命令的輸出結果
二、函數的退出狀態碼:
(1) 默認取決於函數中執行的最後一條命令的退出狀態碼
(2) 自定義退出狀態碼,其格式爲:
return 從函數中返回,用最後狀態命令決定返回值:
(1)return 0 無錯誤返回。
(2)return 1-255 有錯誤返回
執行一條命令的時候, 好比 pwd 正常狀況下它輸出的結果是 當前所處的目錄
$ pwd /Users/chenshang
shell中必然有一種狀態來標識一條命令是否執行成功,也就是命令執行結果的狀態。
0表明真、成功的含義。
非零表明假、失敗的含義。
因此 pwd 這條命令若是執行成功的話,命令的執行結果狀態必定是0,而後返回值纔是當前目錄。若是這條命令執行失敗的話,命令的執行結果狀態必定不是0,有多是1 表明命令不存在,而後輸出 not found,也有可能執行結果狀態是2表明超時,而後什麼也不輸出。那怎麼獲取這個命令的執行結果和執行結果的狀態呢?
function main(){ pwd }
執行main函數就會在控制檯輸出當前目錄 若是想要將pwd的內容獲取到變量中以供後續使用呢
function main(){ local dir=$(pwd) echo "dir is ${dir}" }
若是想要獲取pwd的執行結果的狀態呢
function main(){ local dir=$(pwd) local status=$? echo "pwd run status is ${status}" #這個stauts必定有值,且是int類型,取值範圍在0-255之間 echo "dir is ${dir}" }
return 用來顯示的返回函數的返回結果,例如
# 檢查當前系統版本 [Integer]<-() function check_version(){ (log_info "check_version ...") # log_info是我寫的工具類中的一個函數 local version # 這裏是先定義變量,在對變量進行賦值,咱們每每是直接初始化,而不是像這樣先定義在賦值,這裏只是告訴你們能夠這麼用 version=$(sed -r 's/.* ([0-9]+)\..*/\1/' /etc/redhat-release) (log_info "centos version is ${version}") return "${version}" }
這樣這個函數的返回值是一個數值類型,我在腳本的任何地方調用check_version這個函數後,使用 $? 獲取返回值
check_version local version=$? echo "${version}"
注意這裏不用 local version=$(check_version) 這種形式獲取結果,這樣也是獲取不到結果的,由於顯示的return結果,返回值只能是[0-255]的數值,這對於咱們通常的函數來講就足夠了,由於咱們使用顯示return的時候每每是知道返回結果必定是數字且在[0-255]之間的,經常用在狀態判斷的時候。
本shell規約規定:
明確返回結果是在[0-255]之間的數值類型的時候使用顯示 reuturn 返回結果
返回結果類型是Boolean類型,也就是說函數的功能是起判斷做用,返回結果是真或者假的時候使用顯示 return 返回結果
# 檢查網絡 [Boolean]<-() function check_network(){ (log_info "check_network ...") for((i=1;i<=3;i++));do http_code=$(curl -I -m 10 -o /dev/null -s -w %\{http_code\} www.baidu.com) if [[ ${http_code} -eq 200 ]];then (log_info "network is ok") return ${TRUE} fi done (log_error "network is not ok") return ${FALSE} } # 獲取數組中指定元素的下標 [int]<-(TABLE:Array,target:String) function get_index_of(){ readonly local array=($1) local target=$2 local index=-1 # -1實際上是255 local size=${#array[@]} for ((i=0;i<${size};i++));do if [[ ${array[i]} == ${target} ]];then return ${i} fi done return ${index} }
return 用來顯示的返回函數的返回結果,例如
# 將json字符串格式化樹形結構 [String]<-(json_string:String) function json_format(){ local json_string=$1 echo "${json_string}"|jq . #jq是shell中處理json的一個工具 }
函數中全部的echo照理都應該輸出到控制檯上 例如
json_format "{\"1\":\"one\"}"
你會在控制檯上看到以下輸出
{ "1": "one" }
可是一旦你用變量接收函數的返回值,這些本該輸出到控制檯的結果就都會存儲到你定義的變量中 例如
json=$(json_format "{\"1\":\"one\"}") echo "${json}" # 若是沒有這句,上面的語句執行完成後,不會在控制檯有任何的輸出
咱們把 json_format 改造一下
# 將json字符串格式化樹形結構 [String]<-(json_string:String) function json_format(){ local json_string=$1 echo "爲格式化以前:${json_string}" # 其實新添加的只一句只是用來記錄一行日誌的 echo "${json_string}"|jq . # jq是shell中處理json的一個工具 }
echo "爲格式化以前:${json_string}" 其實新添加的只一句只是用來記錄一行日誌的,可是json=$(json_format "{"1":"one"}") json 也會將這句話做爲返回結果進行接收,但這是我不想要看到的。
子shell能夠捕獲父shell的變量,但不能改變父shell的變量,使用()將代碼塊包裹,包裹的代碼塊將在子shell中運行,子shell至關於獨立的一個環境,不會影響到父shell的結果
因此若是我不想讓 echo "爲格式化以前:${json_string}" 這句話也做爲結果的話,我就只須要用()將代碼塊包裹便可
# 將json字符串格式化樹形結構 [String]<-(json_string:String) function json_format(){ local json_string=$1 (echo "爲格式化以前:${json_string}") # 其實新添加的只一句只是用來記錄一行日誌的 echo "${json_string}"|jq . # jq是shell中處理json的一個工具 }
① 對不一樣的成績分段進行判斷
[root@oldboyedu-lnb ~]# cat functions func_is_digit(){ # 判斷參數$1不是空,就爲真,取反,空爲真 if [ ! "$1" ];then # 請輸入數字 echo "Usage:func_is_digit number" return 10 # 若是輸入是數字,返回0 elif [[ $1 =~ ^[[:digit:]]+$ ]];then return 0 else # 不然提醒不是數字 echo "Not a digit" return 1 fi }
[root@oldboyedu-lnb ~]# cat score.sh
② function配合case:代碼發佈與回滾與檢驗