bash腳本程式設計之使用者互動:
read [option]… [name …]
-p ‘PROMPT’
-t TIMEOUT
bash -n /path/to/some_script
檢測腳本中的文法錯誤
bash -x /path/to/some_script
調試執行
示例:
#!/bin/bash
# Version: 0.0.1
# Author: mrlapulga
# Description: read testing
read -p "Enter a disk special file: " diskfile
[ -z "$diskfile" ] && echo "Fool" && exit 1
if fdisk -l | grep "^Disk $diskfile" &> /dev/null; then
fdisk -l $diskfile
else
echo "Wrong disk special file."
exit 2
fi
bash腳本程式設計1:
CONDITION:
bash指令:
用指令的執行狀态結果;
成功:true
失敗:flase
成功或失敗的意義:取決于用到的指令;
if語句:
單分支:
if CONDITION; then
if-true
fi
雙分支:
else
if-false
多分支:
if CONDITION1; then
elif CONDITION2; then
if-ture
elif CONDITION3; then
…
esle
all-false
逐條件進行判斷,第一次遇為“真”條件時,執行其分支,而後結束;
示例:使用者鍵入檔案路徑,腳本來判斷檔案類型;
#!/bin/bash
#
read -p "Enter a file path: " filename
if [ -z "$filename" ]; then
echo "Usage: Enter a file path."
exit 2
fi
if [ ! -e $filename ]; then
echo "No such file."
exit 3
fi
if [ -f $filename ]; then
echo "A common file."
elif [ -d $filename ]; then
echo "A directory."
elif [ -L $filename ]; then
echo "A symbolic file."
else
echo "Other type."
fi
注意:if語句可嵌套;
循環:for
循環體:要執行的代碼;可能要執行n遍;
進入條件:
退出條件:
for循環:
for 變量名 in 清單; do
循環體
done
執行機制:
依次将清單中的元素指派給“變量名”; 每次指派後即執行一次循環體; 直到清單中的元素耗盡,循環結束;
示例:添加10個使用者, user1-user10;密碼同使用者名;
#!/bin/bash
#
if [ ! $UID -eq 0 ]; then
echo "Only root."
exit 1
fi
for i in {1..10}; do
if id user$i &> /dev/null; then
echo "user$i exists."
else
useradd user$i
if [ $? -eq 0 ]; then
echo "user$i" | passwd --stdin user$i &> /dev/null
echo "Add user$i finished."
fi
fi
done
清單生成方式:
(1) 直接給出清單;
(2) 整數清單:
(a) {start..end}
(b) $(seq [start [step]] end)
(3) 傳回清單的指令;
$(COMMAND)
(4) glob
(b) 變量引用;
$@, $*
示例:判斷某路徑下所有檔案的類型
#!/bin/bash
#
for file in $(ls /var); do
if [ -f /var/$file ]; then
echo "Common file."
elif [ -L /var/$file ]; then
echo "Symbolic file."
elif [ -d /var/$file ]; then
echo "Directory."
else
echo "Other type."
fi
done
示例:
#!/bin/bash
#
declare -i estab=0
declare -i listen=0
declare -i other=0
for state in $( netstat -tan | grep "^tcp\>" | awk '{print $NF}'); do
if [ "$state" == 'ESTABLISHED' ]; then
let estab++
elif [ "$state" == 'LISTEN' ]; then
let listen++
else
let other++
fi
done
echo "ESTABLISHED: $estab"
echo "LISTEN: $listen"
echo "Unkown: $other"
bash腳本程式設計2:
程式設計語言:
資料結構
順序執行
選擇執行
條件測試
運作指令或[[ EXPRESSION ]]
執行狀态傳回值;
if
case
循環執行
将某代碼段重複運作多次;
重複運作多少次?
循環次數事先已知:
循環次數事先未知;
必須有進入條件和退出條件:
for, while, until
函數:結構化程式設計及代碼重用;
function
for循環文法:
for NAME in LIST; do
循環體
done
清單生成方式:
(1) 整數清單
{start..end}
$(seq start [[step]end])
(2) glob
/etc/rc.d/rc3.d/K*
(3) 指令
示例:通過ping指令探測172.16.250.1-254範圍内的所有主機的線上狀态;
#!/bin/bash
#
net='172.16.250'
uphosts=0
downhosts=0
for i in {1..20}; do
ping -c 1 -w 1 ${net}.${i} &> /dev/null
if [ $? -eq 0 ]; then
echo "${net}.${i} is up."
let uphosts++
else
echo "${net}.${i} is down."
let downhosts++
fi
done
echo "Up hosts: $uphosts."
echo "Down hosts: $downhosts."
while循環:
while CONDITION; do
CONDITION:循環控制條件;進入循環之前,先做一次判斷;每一次循環之後會再次做判斷;
條件為“true”,則執行一次循環;直到條件測試狀态為“false”終止循環;
是以:CONDTION一般應該有循環控制變量;而此變量的值會在循環體不斷地被修正;
示例:求100以内所有正整數之和;
#!/bin/bash
#
declare -i sum=0
declare -i i=1
while [ $i -le 100 ]; do
let sum+=$i
let i++
done
echo "$i"
echo "Summary: $sum."
練習:添加10個使用者
user1-user10
#!/bin/bash
#
declare -i i=1
declare -i users=0
while [ $i -le 10 ]; do
if ! id user$i &> /dev/null; then
useradd user$i
echo "Add user: user$i."
let users++
fi
let i++
done
echo "Add $users users."
練習:通過ping指令探測172.16.250.1-254範圍内的所有主機的線上狀态;(用while循環)
#!/bin/bash
#
declare -i i=1
declare -i uphosts=0
declare -i downhosts=0
net='172.16.250'
while [ $i -le 20 ]; do
if ping -c 1 -w 1 $net.$i &> /dev/null; then
echo "$net.$i is up."
let uphosts++
else
echo "$net.$i is down."
let downhosts++
fi
let i++
done
echo "Up hosts: $uphosts."
echo "Down hosts: $downhosts."
練習:列印九九乘法表;(分别使用for和while循環實作)
#!/bin/bash
#
for j in {1..9}; do
for i in $(seq 1 $j); do
echo -e -n "${i}X${j}=$[$i*$j]\t"
done
echo
done
#!/bin/bash
#
declare -i i=1
declare -i j=1
while [ $j -le 9 ]; do
while [ $i -le $j ]; do
echo -e -n "${i}X${j}=$[$i*$j]\t"
let i++
done
echo
let i=1
let j++
done
練習:利用RANDOM生成10個随機數字,輸出這個10數字,并顯示其中的最大者和最小者;
#!/bin/bash
#
declare -i max=0
declare -i min=0
declare -i i=1
while [ $i -le 9 ]; do
rand=$RANDOM
echo $rand
if [ $i -eq 1 ]; then
max=$rand
min=$rand
fi
if [ $rand -gt $max ]; then
max=$rand
fi
if [ $rand -lt $min ]; then
min=$rand
fi
let i++
done
echo "MAX: $max."
echo "MIN: $min."
bash腳本程式設計3:
while CONDITION; do
循環體
done
進入條件:CONDITION為true;
退出條件:false
until CONDITION; do
進入條件:false
退出條件:true
示例:求100以内所正整數之和:
#!/bin/bash
#
declare -i i=1
declare -i sum=0
until [ $i -gt 100 ]; do
let sum+=$i
let i++
done
echo “Sum: $sum”
示例:列印九九乘法表
#!/bin/bash
#
declare -i j=1
declare -i i=1
until [ $j -gt 9 ]; do
until [ $i -gt $j ]; do
echo -n -e "${i}X${j}=$[$i*$j]\t"
let i++
done
echo
let i=1
let j++
done
循環控制語句(用于循環體中):
continue [N]:提前結束第N層的本輪循環,而直接進入下一輪判斷;
while CONDTIITON1; do
CMD1
…
if CONDITION2; then
continue
fi
CMDn
break [N]:提前結束循環;
break
示例1:求100以内所有偶數之和;要求循環周遊100以内的所正整數;
#!/bin/bash
#
declare -i i=0
declare -i sum=0
until [ $i -gt 100 ]; do
let i++
if [ $[$i%2] -eq 1 ]; then
continue
fi
let sum+=$i
done
echo "Even sum: $sum"
建立死循環:
while true; do
until false; do
示例2:每隔3秒鐘到系統上擷取已經登入的使用者的資訊;如果docker登入了,則記錄于日志中,并退出;
#!/bin/bash
#
read -p "Enter a user name: " username
while true; do
if who | grep "^$username" &> /dev/null; then
break
fi
sleep 3
done
echo "$username logged on." >> /tmp/user.log
第二種實作:
#!/bin/bash
#
read -p "Enter a user name: " username
until who | grep "^$username" &> /dev/null; do
sleep 3
done
echo "$username logged on." >> /tmp/user.log
while循環的特殊用法(周遊檔案的每一行):
while read line; do
done < /PATH/FROM/SOMEFILE
依次讀取/PATH/FROM/SOMEFILE檔案中的每一行,且将行指派給變量line:
示例:找出其ID号為偶數的所有使用者,顯示其使用者名及ID号;
#!/bin/bash
#
while read line;do
if [ $[`echo $line | cut -d: -f3` % 2] -eq 0 ];then
echo -e -n "username: `echo $line | cut -d: -f1`\t"
echo "uid: `echo $line | cut -d: -f3 `"
fi
done < /etc/passwd
for循環的特殊格式:
for ((控制變量初始化;條件判斷表達式;控制變量的修正表達式)); do
控制變量初始化:僅在運作到循環代碼段時執行一次;
控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而後再做條件判斷;
示例1:求100以内所正整數之和;
#!/bin/bash
#
declare -i sum=0
for ((i=1;i<=100;i++)); do
let sum+=$i
done
echo "Sum: $sum."
示例2:列印九九乘法表;
#!/bin/bash
#
for((j=1;j<=9;j++));do
for((i=1;i<=j;i++))do
echo -e -n "${i}X${j}=$[$i*$j]\t"
done
echo
done
練習:寫一個腳本,完成如下任務
(1) 顯示一個如下菜單:
cpu) show cpu information;
mem) show memory information;
disk) show disk information;
quit) quit
(2) 提示使用者選擇選項;
(3) 顯示使用者選擇的内容;
#!/bin/bash
#
cat << EOF
cpu) show cpu information;
mem) show memory information;
disk) show disk information;
quit) quit
============================
EOF
read -p "Enter a option: " option
while [ "$option" != 'cpu' -a "$option" != 'mem' -a "$option" != 'disk' -a "$option" != 'quit' ]; do
read -p "Wrong option, Enter again: " option
done
if [ "$option" == 'cpu' ]; then
lscpu
elif [ "$option" == 'mem' ]; then
cat /proc/meminfo
elif [ "$option" == 'disk' ]; then
fdisk -l
else
echo "Quit"
exit 0
fi
進一步地:
使用者選擇,并顯示完成後不退出腳本;而是提示使用者繼續選擇顯示其它内容;直到使用quit方始退出;
條件判斷:case語句
case 變量引用 in
PAT1)
分支1
;;
PAT2)
分支2
…
*)
預設分支
esac
case支援glob風格的通配符:
*: 任意長度任意字元;
?: 任意單個字元;
[]:指定範圍内的任意單個字元;
a|b: a或b
示例:
#!/bin/bash
#
cat << EOF
cpu) show cpu information;
mem) show memory information;
disk) show disk information;
quit) quit
============================
EOF
read -p "Enter a option: " option
while [ "$option" != 'cpu' -a "$option" != 'mem' -a "$option" != 'disk' -a "$option" != 'quit' ]; do
read -p "Wrong option, Enter again: " option
done
case "$option" in
cpu)
lscpu
;;
mem)
cat /proc/meminfo
;;
disk)
fdisk -l
;;
*)
echo "Quit..."
exit 0
;;
esac
bash腳本程式設計4:
function:函數
過程式程式設計:代碼重用
子產品化程式設計
結構化程式設計
文法一:
function f_name {
…函數體…
}
文法二:
f_name() {
}
調用:函數隻有被調用才會執行;
調用:給定函數名
函數名出現的地方,會被自動替換為函數代碼;
函數的生命周期:被調用時建立,傳回時終止;
return指令傳回自定義狀态結果;
0:成功
1-255:失敗
#!/bin/bash
#
function adduser {
if id $username &> /dev/null; then
echo "$username exists."
return 1
else
useradd $username
[ $? -eq 0 ] && echo "Add $username finished." && return 0
fi
}
for i in {1..10}; do
username=myuser$i
adduser
done
示例:服務腳本
#!/bin/bash
#
# chkconfig: - 88 12
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
start() {
if [ -e $lockfile ]; then
echo "$prog is aleady running."
return 0
else
touch $lockfile
[ $? -eq 0 ] && echo "Starting $prog finished."
fi
}
stop() {
if [ -e $lockfile ]; then
rm -f $lockfile && echo "Stop $prog ok."
else
echo "$prog is stopped yet."
fi
}
status() {
if [ -e $lockfile ]; then
echo "$prog is running."
else
echo "$prog is stopped."
fi
}
usage() {
echo "Usage: $prog {start|stop|restart|status}"
}
if [ $# -lt 1 ]; then
usage
exit 1
fi
case $1 in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
usage
esac
函數傳回值:
函數的執行結果傳回值:
(1) 使用echo或print指令進行輸出;
(2) 函數體中調用指令的執行結果;
函數的退出狀态碼:
(1) 預設取決于函數體中執行的最後一條指令的退出狀态碼;
(2) 自定義退出狀态碼:
return
函數可以接受參數:
傳遞參數給函數:調用函數時,在函數名後面以空白分隔給定參數清單即可;例如“testfunc arg1 arg2 …”
在函數體中當中,可使用$1, $2, …調用這些參數;還可以使用$@, $*, $#等特殊變量;
示例:添加10個使用者
#!/bin/bash
#
function adduser {
if [ $# -lt 1 ]; then
return 2
# 2: no arguments
fi
if id $1 &> /dev/null; then
echo "$1 exists."
return 1
else
useradd $1
[ $? -eq 0 ] && echo "Add $1 finished." && return 0
fi
}
for i in {1..10}; do
adduser myuser$i
done
變量作用域:
本地變量:目前shell程序;為了執行腳本會啟動專用的shell程序;是以,本地變量的作用範圍是目前shell腳本程式檔案;
局部變量:函數的生命周期;函數結束時變量被自動銷毀;
如果函數中有局部變量,其名稱同本地變量;
在函數中定義局部變量的方法:
local NAME=VALUE
函數遞歸:
函數直接或間接調用自身;
N!=N(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!
#!/bin/bash
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact 5
練習:求n階斐波那契數列;
#!/bin/bash
#
fab() {
if [ $1 -eq 1 ]; then
echo 1
elif [ $1 -eq 2 ]; then
echo 1
else
echo $[$(fab $[$1-1])+$(fab $[$1-2])]
fi
}
fab 7
bash腳本程式設計5:
數組:
變量:存儲單個元素的記憶體空間;
數組:存儲多個元素的連續的記憶體空間;
數組名
索引:編号從0開始,屬于數值索引;
注意:索引也可支援使用自定義的格式,而不僅僅是數值格式;
bash的數組支援稀疏格式;
引用數組中的元素:${ARRAY_NAME[INDEX]}
聲明數組:
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
引用數組元素:${ARRAY_NAME[INDEX]}
注意:省略[INDEX]表示引用下标為0的元素;
數組的長度(數組中元素的個數):${#ARRAY_NAME[*]}, ${#ARRAY_NAME[@]}
示例:生成10個随機數儲存于數組中,并找出其最大值和最小值;
#!/bin/bash
#
declare -a rand
declare -i max=0
for i in {0..9}; do
rand[$i]=$RANDOM
echo ${rand[$i]}
[ ${rand[$i]} -gt $max ] && max=${rand[$i]}
done
echo "Max: $max"
練習:寫一個腳本
定義一個數組,數組中的元素是/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."
引用數組中的元素:
所有元素:${ARRAY[@]}, ${ARRAY[*]}
數組切片:${ARRAY[@]:offset:number}
offset: 要跳過的元素個數
number: 要取出的元素個數,取偏移量之後的所有元素:${ARRAY[@]:offset};
向數組中追加元素:
ARRAY[${#ARRAY[*]}]
删除數組中的某元素:
unset ARRAY[INDEX]
關聯數組:
declare -A ARRAY_NAME
ARRAY_NAME=([index_name1]=’val1′ [index_name2]=’val2′ …)
bash的字元串處理工具:
字元串切片:
${var:offset:number}
取字元串的最右側幾個字元:${var: -lengh}
注意:冒号後必須有一空白字元;
基于模式取子串:
${var#*word}:其中word可以是指定的任意字元;功能:自左而右,查找var變量所存儲的字元串中,第一次出現的word, 删除字元串開頭至第一次出現word字元之間的所有字元;
${var##*word}:同上,不過,删除的是字元串開頭至最後一次由word指定的字元之間的所有内容;
file=”/var/log/messages”
${file##*/}: messages
${var%word*}:其中word可以是指定的任意字元;功能:自右而左,查找var變量所存儲的字元串中,第一次出現的word, 删除字元串最後一個字元向左至第一次出現word字元之間的所有字元;
${file%/*}: /var/log
${var%%word*}:同上,隻不過删除字元串最右側的字元向左至最後一次出現word字元之間的所有字元;
示例:url=http://www.mrlapulga.com:80
${url##*:}
${url%%:*}
查找替換:
${var/pattern/substi}:查找var所表示的字元串中,第一次被pattern所比對到的字元串,以substi替換之;
${var//pattern/substi}: 查找var所表示的字元串中,所有能被pattern所比對到的字元串,以substi替換之;
${var/#pattern/substi}:查找var所表示的字元串中,行首被pattern所比對到的字元串,以substi替換之;
${var/%pattern/substi}:查找var所表示的字元串中,行尾被pattern所比對到的字元串,以substi替換之;
查找并删除:
${var/pattern}:查找var所表示的字元串中,删除第一次被pattern所比對到的字元串
${var//pattern}:
${var/#pattern}:
${var/%pattern}:
字元大小寫轉換:
${var^^}:把var中的所有小寫字母轉換為大寫;
${var,,}:把var中的所有大寫字母轉換為小寫;
變量指派:
${var:-value}:如果var為空或未設定,那麼傳回value;否則,則傳回var的值;
${var:=value}:如果var為空或未設定,那麼傳回value,并将value指派給var;否則,則傳回var的值;
${var:+value}:如果var不空,則傳回value;
${var:?error_info}:如果var為空或未設定,那麼傳回error_info;否則,則傳回var的值;
為腳本程式使用配置檔案:
(1) 定義文本檔案,每行定義“name=value”
(2) 在腳本中source此檔案即可
指令:
mktemp指令:
mktemp [OPTION]… [TEMPLATE]
TEMPLATE: filename.XXX
XXX至少要出現三個;
OPTION:
-d: 建立臨時目錄;
–tmpdir=/PATH/TO/SOMEDIR:指明臨時檔案目錄位置;
install指令:
install [OPTION]… [-T] SOURCE DEST
install [OPTION]… SOURCE… DIRECTORY
install [OPTION]… -t DIRECTORY SOURCE…
install [OPTION]… -d DIRECTORY…
選項:
-m MODE
-o OWNER
-g GROUP