1. 程式人生 > >朗科學習期間心得筆記(九)

朗科學習期間心得筆記(九)

網絡 基礎 運維


bash腳本編程的結構:
bash腳本編程語言:
腳本類語言
解釋型語言
過程式編程語言

過程式編程語言的結構:
順序執行結構:從上到下,從左向右的執行所有語句(命令);
選擇執行結構:當條件滿足或不滿足時,才會執行對應的語句(命令);
循環執行結構:重復執行某段語句(命令);

bash腳本編程語言中,也具備上述結構;其默認為順序執行結構;


選擇執行結構:根據給定條件的邏輯判斷結果,或根據某個可選取的取值範圍,進而選擇某個分支結構中的命令語句予以執行的方式;
if:
選擇執行結構的標準,根據條件的邏輯判斷結果選擇執行的語句內容;
case:
選擇執行結構的標準,根據符合某特定範圍的取值標準選擇執行的語句內容;

循環執行結構:對於特定語句內容,重復執行0次,1次或多次;
for:以遍歷列表的方式進行循環;
while:根據給定條件的邏輯判斷結果;邏輯判斷結果為真才循環,否則停止循環;
until:根據給定條件的邏輯判斷結果;邏輯判斷結果為假才循環,否則停止循環;
select:死循環,即沒有默認退出條件的循環;利用循環提供一個可選擇的列表;例如: $RANDOM



bash腳本的執行結構---if選擇執行結構
if 命令; then 命令; [ elif 命令; then 命令; ]... [ else 命令; ] fi

if語句的單分支結構
if 命令; then 命令; fi
註意:是否會執行then後的命令,取決於if後命令的狀態返回值,如果其返回值為真則執行then後的命令,否則不執行then後的命令;

建議在腳本中的書寫格式:
if CONDITION(條件) ;then
STATEMENT
...
fi

if語句的雙分支結構
if 命令; then 命令; [ else 命令; ] fi

註意:如果其返回值為真則執行then後的命令,返回值為假則執行else後的命令;
if CONDITION(條件) ;then
STATEMENT
...
else
...
fi


if語句的多分支結構
if 命令; then 命令; [ elif 命令; then 命令; ]... [ else 命令; ] fi

註意:是否會執行then後的命令,取決於if後命令的狀態返回值或elif後命令的狀態返回值,如果if為真則執行then後命令,如果為假則繼續判斷第一個elif後命令的返回值,第一個elif返回值為真則執行第一個elif語句中then後命令,否則判斷第二個elif後命令的返回值.....如果所有的if,elif後的命令返回值都為假,則執行else後ude命令;


if CONDITION(條件) ;then
STATEMENT
...
elif CONDITION2 ; then
STATEMENT
...
elif CONDITION2 ; then
STATEMENT
...
...
else
...
fi
註意:if的多分支結構,使用場景不多,而且有些時候,可以使用嵌套的單分支或雙分支if結構代替多分支結構;

嵌套if結構:
if condition1 ; then
if condition2 ; then
if condition3 ; then
statement
...
else
statement
...
fi
else
statement
...
fi
else
statement
...
fi

示例:
1.寫一個腳本,判斷某個用戶的默認登錄shell是否為/bin/bash;
#!/bin/bash
#
USERNAME=$(cut -d: -f1 /etc/shadow | sort -R | head -1)
USERSHELL=$(egrep "^$USERNAME\>" /etc/passwd | cut -d: -f7)
if [ "$USERSHELL" == "/bin/bash" ] ; then
echo "${USERNAME}'s login shell is /bin/bash."
fi

unset USERNAME USERSHELL

2.寫一個腳本,判斷某個用戶的默認登錄shell是否為/bin/bash,如果不是,顯示其登錄shell
#!/bin/bash
#
USERNAME=$(cut -d: -f1 /etc/shadow | sort -R | head -1)
USERSHELL=$(egrep "^$USERNAME\>" /etc/passwd | cut -d: -f7)
if [ "$USERSHELL" == "/bin/bash" ] ; then
echo "${USERNAME}'s login shell is /bin/bash."
else
echo "${USERNAME}'s login shell is ${USERSHELL}"
fi

unset USERNAME USERSHELL



bash腳本編程---用戶交互使用
位置參數變量:
$0:命令的本身,對於腳本而言,就是該腳本的路徑;
$1,$2,...$n:腳本後面通過命令行給腳本傳遞的命令行參數;
n>9時,引用該位置變量時需要加 {} 即:${10}


特殊變量:
$@:給出的所有位置參數的列表,當使用雙引號引用時,每個參數作為單獨字符串存在;
$*:給出的所有位置參數的列表,當使用雙引號引用時,整個參數列表被當作字符串;
$#:表示除去$0之外,整個命令行中有多少個參數;即 參數的個數;




read命令:
Read a line from the standard input and split it into fields.
read - read [-a array] [-p prompt] [-t timeout] [name ...]
-a array:定義索引數組;
-p prompt:給用戶輸出提示信息;
-t timeout:用戶輸入的超時時間;
name:變量或數組的名稱;如果省略此內容,bash會將read讀到的信息直接保存到內置的名為REPLY變量中;

註意:
linux哲學思想之一:盡量不與用戶交互;

在使用read命令時,通常會使用-t選項來指定與用戶交互的時間,一旦時間超過預定時間,腳本中後續的命令內容會自動被執行;因此,通常需要在後面判斷通過read賦值的變量值是否為空,如果為空,可能需要為該變量提供默認值;
read -t 5 VAR1
[ -z $VAR1 ] && VAR1=value1


管理用戶腳本:
腳本可以接受兩個參數,第一個參數為-a或-d,第二個參數為用戶名;如果第一個參數是-a,則創建其後面參數命名的用戶;如果第一個參數為-d,則刪除其後面參數命名的用戶;

#!/bin/bash
#
if [ $# -ne 2 ] ; then
echo "Make sure provide TWO arguments."
exit 5
fi

if [ $1 == '-a' ] ; then
if ! id $2 &> /dev/null ; then
useradd $2 &> /dev/null
echo $2 | passwd --stdin $2 &> /dev/null
echo "User $2 created succesfully and password changed to it's username."
else
echo "$2 exists already."
fi
elif [ $1 == '-d' ] ; then
if id $2 &> /dev/null ; then
userdel -r $2 &> /dev/null
echo "User $2 delete finished."
else
echo "$2 does not exist yet."
fi
else
echo "Usage: $(basename $0) -a USERNAME | -d USERNAME"
exit 6
fi

改進版:使用read命令;
#!/bin/bash
#
if [ $# -ne 1 ] ; then
echo "Make sure provide ONE argument."
exit 5
fi

if [ $1 == '-a' ] ; then
read -p "Please input a username for creating: " USERNAME
if ! id $USERNAME &> /dev/null ; then
useradd $USERNAME &> /dev/null
echo $USERNAME | passwd --stdin $USERNAME &> /dev/null
echo "User $USERNAME created succesfully and password changed to it's username."
else
echo "$USERNAME exists already."
fi
elif [ $1 == '-d' ] ; then
read -p "Please input a username for deleting: " USERNAME
read -t 5 -p "confirm? Input 'yes' to continue: " CHOICE
[ -z $CHOICE ] && CHOICE='no'

if [ $CHOICE == 'yes' ] ; then
if id $USERNAME &> /dev/null ; then
userdel -r $USERNAME &> /dev/null
echo "User $USERNAME delete finished."
else
echo "$USERNAME does not exist yet."
fi
else
echo
echo "$USERNAME is not deleted."
fi
else
echo "Usage: $(basename $0) { -a | -d }"
exit 6
fi

寫腳本解決問題:
1.判斷用戶通過命令行給的一個參數是否為整數。

#!/bin/bash
#
if [ $# -ne 1 ] ; then
echo "Make sure provide ONE digit."
exit 5
fi

# if ! [[ $1 =~ [^[:digit:]] ]] ; then
if [[ $1 =~ ^[[:digit:]]+$ ]] ; then
echo "$1 is a pure digit."
else
echo "$1 is not a digit."
fi

循環執行結構:
循環:將某一段代碼或命令重復執行0次,1次或多次;

一個好的循環結構,必須要包括兩個重要的環節:
1.進入循環的條件:
在符合要求或滿足條件時才開始循環;

2.退出循環的條件
達到某個要求或符號某個條件時需要結束或終止循環的執行;

for循環:
1.遍歷列表的循環:
Execute commands for each member in a list.
for - for NAME [in WORDS ... ] ; do COMMANDS; done

建議在腳本中的書寫格式:
for VAR_NAME in LIST ; do
循環體
done

for VAR_NAME in LIST
do
循環體
done

註意:
VAR_NAME:任意指定的變量名稱,變量的值是從LIST中遍歷獲取的各個元素;
LIST:for循環需要遍歷的列表;可以通過以下方式生成列表:
1.直接給出列表;
2.純整數列表:
1) 花括號展開:
{FIRSTNUMM..LASTNUM}
{FIRST,SECOND,THIRD,....,LAST}
2) seq命令
seq [OPTION]... LAST
seq [OPTION]... FIRST LAST
seq [OPTION]... FIRST INCREMENT LAST
3.花括號展開:
{FIRST..LAST}
4.命令的執行結果:
ls /etc
grep /PATH/TO/SOMEFILE
5.GLOBBING
6.某些特殊變量的值:
$*, $@
循環體:
一般來說,循環體中應該包括能夠用到VAR_NAME變量的值的命令或命令的組合;如果循環體中的命令並沒有用到VAR_NAME變量的值的話,列表的元素個數就是此次for循環的次數;

寫一個腳本,計算1到100的數字之和;
#!/bin/bash
#
declare -i SUM=0
for I in {1..100} ; do
SUM=$[SUM+I]
done
echo "The summary is: $SUM"


寫一個腳本,能夠通過-a或-d選項添加或刪除一個或多個用戶賬戶;
#!/bin/bash
#
if [ $# -lt 2 ] ; then
echo "Make sure provide more than TWO arguments."
exit 5
fi

if [ $1 == '-a' ] ; then
shift
for I in "$@" ; do
if ! id $I &> /dev/null ; then
useradd $I &> /dev/null
echo $I | passwd --stdin $I &> /dev/null
echo "User $I created succesfully and password changed to it's username."
else
echo "$I exists already."
fi
done
elif [ $1 == '-d' ] ; then
shift
for J in "$@" ; do
if id $J &> /dev/null ; then
userdel -r $J &> /dev/null
echo "User $J delete finished."
else
echo "$J does not exist yet."
fi
done
else
echo "Usage: $(basename $0) -a UNAME1 [UNAME2 ...] | -d UNAME1 [UNAME2 ...]"
exit 6
fi

總結:
1.進入循環的條件:LIST中尚有未被取盡的元素;
2.退出循環的條件:LSIT中的元素被取盡;
3.for循環幾乎不會出現死循環;
4.在執行循環的過程中,需要將整個LIST載入內存,因此,對於大列表來說,可能會消耗較多的內存及CPU資源;

計算指定數字範圍內自然數的和:
#!/bin/bash
#
declare -i SUM=0
read -p "Please input TWO integer: " INT1 INT2
if [[ $INT1 =~ [^[:digit:]] ]] ; then
echo "$INT1 must be an integer."
exit 5
fi

if [[ $INT2 =~ [^[:digit:]] ]] ; then
echo "$INT2 must be an integer."
exit 5
fi

if [ $INT1 -gt $INT2 ] ; then
for I in $(seq $INT2 $INT1) ; do
# SUM=$[SUM+I]
let SUM+=$I
done
echo "The summary is: $SUM"
else
for I in $(seq $INT1 $INT2) ; do
# SUM=$[SUM+I]
let SUM+=$I
done
echo "The summary is: $SUM"
fi

寫一個腳本,打印有"*"組成的倒置的等腰三角形;
********* 1行,0個空白字符,9個*
******* 2行,1個空白字符,7個*
***** 3行,2個空白字符,5個*
*** 4行,3個空白字符,3個*
* 5行,4個空白字符,1個*

N行,N-1個空白字符,2*(總行數-當前行號)+1 個*
#!/bin/bash
#
if [ $# -ne 1 ] ; then
echo "Usage: $(basename $0) INTEGER"
exit 5
fi

if [[ $1 =~ [^[:digit:]] ]] ; then
echo "Usage: $(basename $0) INTEGER"
exit 6
fi

LINENUM=$1
for I in $(seq $LINENUM) ; do
for J in $(seq $[I-1]) ; do
echo -n " "
done
for K in $(seq $[2*(LINENUM-I)+1]) ; do
echo -n "*"
done
echo
done

打印九九乘法表
第一行:1個
第二行:2個
...
第九行:9個

#!/bin/bash
#
for I in {1..9} ; do
for J in $(seq $I) ; do
echo -ne "$I×$J=$[I*J]\t"
done
echo
done

#!/bin/bash
#
for (( I=1 ; I<=9 ; I++ )) ; do
for (( J=1 ; J<=I ; J++ )) ; do
echo -ne "$J×$I=$[I*J]\t"
done
echo
done

以上兩個例子,均使用for循環的嵌套;往往需要兩層的循環嵌套才能打印出平面效果;外層的for循環,負責控制行數輸出;內層的for循環,負責控制每一行中各個列的輸出;

2.通過控制變量實現for循環:
for (( exp1; exp2; exp3 )); do COMMANDS; done
可以在腳本中寫成如下格式:
for (( exp1; exp2; exp3 )); do
COMMANDS
done

exp1:表達式1,為指定的變量賦初始值;
exp2:表達式2,此次循環的退出條件;
exp3:表達式3,指定的變量的值的變化規律;

計算從1到100的自然數的和;
#!/bin/bash
#
for (( I=1 ; I<=100 ; I++ )) ; do
let SUM+=$I
done
echo "The summary is: $SUM"

編程思想:
將人類的自然語言轉換成程序的代碼語言的方式;



朗科學習期間心得筆記(九)