迴圈基礎知識
在 Shell 指令碼中,迴圈是一種控制結構,用於重複執行一段程式碼多次。常用的有 for 迴圈、while迴圈、until迴圈,三者之間的效能差異不是特別明顯,主要取決於具體的使用場景和迴圈體的複雜性。for 迴圈用於遍歷固定數量的元素(如陣列、列表等),通常用在已知迭代次數的場景。while 迴圈在條件為真時執行,而 until 迴圈在條件為假時執行。這意味著它們在邏輯上是相對的,選擇哪種迴圈更多是基於程式設計的可讀性。
for 迴圈
for迴圈基礎
for 迴圈的語法如下所示for variable in list
do
# 需要執行的命令
done
示例:
簡單來說 for 迴圈會將 in 後面的所有空格分隔的內容都作為單個引數一個個讀入進來順序執行完。[root@imzcy ~]# cat f.sh
#!/bin/bash
for VAR in 1 2 3 4 5
do
echo "[$(date +%Y%m%d_%H%M%S)] 當前迴圈中 VAR 的值為:$VAR"
sleep 1
done
[root@imzcy ~]#
[root@imzcy ~]# sh f.sh
[20240817_113851] 當前迴圈中 VAR 的值為:1
[20240817_113852] 當前迴圈中 VAR 的值為:2
[20240817_113853] 當前迴圈中 VAR 的值為:3
[20240817_113854] 當前迴圈中 VAR 的值為:4
[20240817_113855] 當前迴圈中 VAR 的值為:5
[root@imzcy ~]#
前面講過 for 迴圈通常用在已知迭代次數的場景中,接下來我們給出一些實際案例。
for迴圈之基於數值的迴圈
主要用在想要迴圈指定次數的場景中,透過 seq 命令或者 {} 都可以實現。
透過 seq 命令[root@imzcy ~]# cat f2.sh
#!/bin/bash
for VAR in $( seq 1 5 )
do
echo "[$(date +%Y%m%d_%H%M%S)] 當前迴圈中 VAR 的值為:$VAR"
sleep 1
done
[root@imzcy ~]#
[root@imzcy ~]# sh f2.sh
[20240817_115517] 當前迴圈中 VAR 的值為:1
[20240817_115518] 當前迴圈中 VAR 的值為:2
[20240817_115519] 當前迴圈中 VAR 的值為:3
[20240817_115520] 當前迴圈中 VAR 的值為:4
[20240817_115521] 當前迴圈中 VAR 的值為:5
[root@imzcy ~]#
透過 {} 實現
[root@imzcy ~]# cat f3.sh
#!/bin/bash
for VAR in {1..5}
do
echo "[$(date +%Y%m%d_%H%M%S)] 當前迴圈中 VAR 的值為:$VAR"
sleep 1
done
[root@imzcy ~]#
[root@imzcy ~]# sh f3.sh
[20240817_115748] 當前迴圈中 VAR 的值為:1
[20240817_115749] 當前迴圈中 VAR 的值為:2
[20240817_115750] 當前迴圈中 VAR 的值為:3
[20240817_115751] 當前迴圈中 VAR 的值為:4
[20240817_115752] 當前迴圈中 VAR 的值為:5
[root@imzcy ~]#
for迴圈之基於字串的迴圈
主要用在想要迴圈所有已知字元的情況下,可以為空格分隔的多個字串或命令的執行結果。
直接指定空格分隔的多個字串生成json資料[root@imzcy ~]# cat f5.sh
#!/bin/bash
for VAR in zhangsan lisi wangwu
do
JSON_DATA='{"name":"'"${VAR}"'","group":"test-001"}'
echo "[$(date +%Y%m%d_%H%M%S)] 資料為 '$JSON_DATA'"
sleep 1
done
[root@imzcy ~]#
使用命令的執行結果建立備份檔案[root@imzcy ~]# sh f5.sh
[20240817_122120] 資料為 '{"name":"zhangsan","group":"test-001"}'
[20240817_122121] 資料為 '{"name":"lisi","group":"test-001"}'
[20240817_122122] 資料為 '{"name":"wangwu","group":"test-001"}'
[root@imzcy ~]#
[root@imzcy ~]# ls *.txt
example.txt group.txt test.txt user.txt
[root@imzcy ~]#
[root@imzcy ~]# cat f4.sh
#!/bin/bash
for VAR in $( ls /root/*.txt )
do
NEW_VAR="${VAR}.bak"
echo "[$(date +%Y%m%d_%H%M%S)] $VAR 檔案將備份為 $NEW_VAR"
cp $VAR $NEW_VAR
sleep 1
done
[root@imzcy ~]#
[root@imzcy ~]# sh f4.sh
[20240817_120453] /root/example.txt 檔案將備份為 /root/example.txt.bak
[20240817_120454] /root/group.txt 檔案將備份為 /root/group.txt.bak
[20240817_120455] /root/test.txt 檔案將備份為 /root/test.txt.bak
[20240817_120456] /root/user.txt 檔案將備份為 /root/user.txt.bak
[root@imzcy ~]#
[root@imzcy ~]# ls *.bak
example.txt.bak group.txt.bak test.txt.bak user.txt.bak
[root@imzcy ~]#
while 迴圈
while 迴圈基礎
while 迴圈在條件為真時執行,語法格式如下所示示例while [ 條件 ]
do
# 需要執行的命令
done
變數VAR的初始值為1,每次迴圈後自增1,當迴圈了5次後值為6的時候,條件 VAR 值小於等於5 不為真,則退出迴圈。[root@imzcy ~]# cat f6.sh
#!/bin/bash
VAR=1
while [ $VAR -le 5 ]
do
echo "VAR: $VAR"
((VAR++))
done
[root@imzcy ~]#
while 迴圈之死迴圈前面講過 while 迴圈只在 “條件” 為真的時候執行,所以如果想要迴圈一直持續執行的話,最簡單就是條件直接為 true 。[root@imzcy ~]# sh f6.sh
VAR: 1
VAR: 2
VAR: 3
VAR: 4
VAR: 5
[root@imzcy ~]#
while 迴圈之讀取檔案內容while 迴圈還可以用於讀取檔案中的內容,進行資料處理。[root@imzcy ~]# cat f7.sh
#!/bin/bash
while true
do
ping -c 1 -W 2 example.com >/dev/null 2>&1
if [ $? -ne 0 ] ; then
echo "[ $(date +%Y%m%d_%H%M%S) ] 到 example.com 的網路不通!"
fi
sleep 1
done
[root@imzcy ~]#
[root@imzcy ~]# cat ip.txt
192.168.2.11 1723940173
192.168.2.12 1723942573
192.168.2.13 1723947373
[root@imzcy ~]#
[root@imzcy ~]# cat f8.sh
#!/bin/bash
INPUT_FILE=/root/ip.txt
while read NOW_LINE
do
echo "當前行為:$NOW_LINE"
eval $(echo "$NOW_LINE" |awk '{printf("KEY_IP=%s; KEY_TIME=%s",$1,$2)}')
NEW_TIME=$( date -d @${KEY_TIME} "+%Y-%m-%d %H:%M:%S" )
echo "將時間戳轉換為標準時間後行為:[$NEW_TIME] $KEY_IP"
sleep 1
done < $INPUT_FILE
[root@imzcy ~]#
[root@imzcy ~]# sh f8.sh
當前行為:192.168.2.11 1723940173
處理後資料為:[2024-08-18 08:16:13] 192.168.2.11
當前行為:192.168.2.12 1723942573
處理後資料為:[2024-08-18 08:56:13] 192.168.2.12
當前行為:192.168.2.13 1723947373
處理後資料為:[2024-08-18 10:16:13] 192.168.2.13
[root@imzcy ~]#
while 迴圈之讀取變數值
如果變數的值為多行內容,也可以直接 while 迴圈讀取進行處理。
[root@imzcy ~]# cat f9.sh
#!/bin/bash
USER_LIST="張三 zhangsan@example.com
李四 lisi@example.com
王五 wangwu@example.com"
echo "$USER_LIST" | while read NOW_LINE
do
eval $(echo "$NOW_LINE" |awk '{printf("KEY_USER=%s; KEY_MAIL=%s",$1,$2)}')
echo "即將給使用者 ${KEY_USER} 的郵箱 ${KEY_MAIL} 傳送郵件。"
sleep 1
done
[root@imzcy ~]#
使用上面的方式讀取變數中的內容處理資料時,由於使用了管道符號 | ,因此會建立一個新的子shell來執行管道右側的命令,子shell中所做的任何更改(包括變數賦值)都不會影響到主shell。所以這種情況下,while 迴圈中任何的變數賦值以及變數內容的修改都無法傳遞到迴圈外的後續命令的執行。這點需要額外注意。[root@imzcy ~]# sh f9.sh
即將給使用者 張三 的郵箱 zhangsan@example.com 傳送郵件。
即將給使用者 李四 的郵箱 lisi@example.com 傳送郵件。
即將給使用者 王五 的郵箱 wangwu@example.com 傳送郵件。
[root@imzcy ~]#
until 迴圈
until 迴圈基礎
until 迴圈在條件為假時執行,語法格式如下所示
until [ 條件 ]
do
# 需要執行的命令
done
示例
[root@imzcy ~]# cat f10.sh
#!/bin/bash
VAR=0
until [ $VAR -eq 8 ]
do
read -p "請輸入您猜測的數字:" VAR
sleep 1
done
[root@imzcy ~]#
當使用者輸入錯誤的數字時會一直要求重新輸入,直到輸入正確的數字時才會退出迴圈。
[root@imzcy ~]# sh f10.sh
請輸入您猜測的數字:1
請輸入您猜測的數字:2
請輸入您猜測的數字:6
請輸入您猜測的數字:8
[root@imzcy ~]#
正常情況下一般 while 迴圈用的比較多一些,但是當你需要等待某個條件變為真時,使用 until 迴圈是一個理想的選擇。例如,等待某個程序啟動,或者等待某個檔案的存在。
until 迴圈之等待某程序啟動
如下指令碼使用 pgrep 命令作為條件,當指定名稱的程序沒有在執行時,會一直迴圈等待直到條件為真時結束迴圈。
由於不存在 dotnet 程序,所以一直迴圈直到我 <ctrl>+<C> 終止。[root@imzcy ~]# cat f11.sh
#!/bin/bash
PROCESS_NAME="$1"
until pgrep -x "$PROCESS_NAME" > /dev/null
do
echo "程序 $PROCESS_NAME 未執行。請等待此程序啟動!"
sleep 1
done
[root@imzcy ~]#
[
程序 dotnet 未執行。請等待此程序啟動!
程序 dotnet 未執行。請等待此程序啟動!
程序 dotnet 未執行。請等待此程序啟動!
^C
[
由於 mysqld 程序已經在啟動了,所以迴圈直接退出執行了。
[
[
break 和 continue 指令
上面講到的三個迴圈語句中有兩個通用的指令,break 和 continue。break 用於中止當前迴圈的執行。當 break 被執行時,迴圈將被立即終止,程式的控制權將轉移到迴圈後面的程式碼。而 continue 用於跳過當前迴圈的剩餘部分,並立即開始下一次的迴圈迭代。也就是說,當 continue 被執行時,控制權將轉到迴圈的條件部分,而不執行隨後的程式碼。
迴圈之 break 指令測試
[root@imzcy ~]# cat f12-1.sh
#!/bin/bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
[root@imzcy ~]#
如下所示,當 i 的值迴圈到 5 時,則迴圈被終止。
迴圈之 continue 指令測試[
1
2
3
4
[
[root@imzcy ~]# cat f12-2.sh
#!/bin/bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
continue
fi
echo $i
done
[root@imzcy ~]#
如下所示,當 i 的值迴圈到 5 時,則跳過迴圈中後續命令的執行(沒有列印數字5)直接開始下次迴圈。
[
1
2
3
4
6
7
8
9
10
[