Shell 腳本編程:while 循環與 until 循環
循環結構簡介
循環語句是 Shell 腳本中用于重復執行一條或一組指令的重要工具,直到滿足特定條件時停止執行。Shell 腳本中常見的循環語句包括 while、until、for 和 select。本文將重點介紹 while 和 until 兩種循環結構,并通過豐富的示例展示其實際應用場景。
while 循環與 until 循環語法解析
while 循環語法
while 循環屬于“當型”循環結構,其基本語法格式為:
while <條件表達式>
do指令...
done
執行邏輯:
- 首先判斷條件表達式是否成立
- 如果成立,則執行循環體內的指令
- 每次執行到 done 時重新判斷條件表達式
- 直到條件不成立時退出循環
until 循環語法
until 循環屬于"直到型"循環結構,其基本語法格式為:
until <條件表達式>
do指令...
done
執行邏輯:
- 當條件表達式不成立時執行循環體
- 直到條件表達式成立時終止循環
基礎應用示例
示例1:豎向打印數字
while 實現方式:
#!/bin/bash
i=5
while ((i>0)) # 當i大于0時執行循環
doecho $i # 打印當前i值((i--)) # i自減1
done
until 實現方式:
#!/bin/bash
i=5
until ((i==0)) # 直到i等于0時停止循環
doecho $i # 打印當前i值((i--)) # i自減1
done
示例2:計算1-100的累加和
#!/bin/bash
i=1
sum=0
while ((i<=100)) # 當i小于等于100時執行循環
do((sum+=i)) # 累加i到sum變量# let sum=sum+i # 另一種累加方式((i++)) # i自增1# let i++ # 另一種自增方式
done
echo "1+2+3+...+99+100=$sum" # 輸出結果
示例3:計算5的階乘
#!/bin/bash
i=1
sum=1
while ((i<=5)) # 循環5次
do((sum*=i)) # 累乘計算階乘((i++)) # i自增1
done
echo "5的階乘為:$sum" # 輸出結果
示例4:猴子吃桃問題
問題描述:
- 猴子第一天摘下若干個桃子,當即吃了一半,還不過癮,又多吃了一個
- 第二天早上又將第一天剩下的桃子吃掉一半,又多吃了一個
- 以后每天早上都吃了前一天剩下的一半零一個
- 到第10天早上想再吃時,發現只剩下一個桃子了
問:猴子第一天摘了多少個桃子?
while循環解法:
#!/bin/bash
# 當天桃子數量,第10天為1
today=1
# 前一天桃子數量
lastday=0
# 只需要迭代9次(從第10天倒推回第1天)
i=1
while ((i<=9))
do# 計算上一天桃子數量:today = (lastday/2) - 1 → lastday = (today+1)*2lastday=$[(today+1)*2]# 把上一天的數量當作今天的數量,繼續向前推算today=${lastday}((i++))
done
echo "猴子第一天摘的桃子數量是:$today。"
函數遞歸解法:
#!/bin/bash
function sum (){if [[ $1 = 1 ]];thenecho $1 # 第10天只剩1個桃子else# 遞歸計算:第n天的桃子數 = (第n+1天的桃子數 + 1) * 2echo $[ ($(sum $[$1 -1]) + 1)*2 ]fi
}
echo "猴子第一天摘的桃子數量是:$(sum 10)。"
示例5:猜數字游戲
#!/bin/bash
# 生成1-50的隨機數字
random_num=$[ RANDOM%50+1 ]
echo "${random_num}" >> /tmp/number # 保存隨機數(用于調試)# 記錄猜測次數
i=0
while true # 無限循環,直到猜對退出
doread -p "猜一猜系統產生的50以內隨機數是:" numif ((num>=1 && num<=50));then # 驗證輸入有效性((i++)) # 增加猜測次數if [ $num -eq ${random_num} ];thenecho "恭喜你,第$i次猜對了!"rm -f /tmp/number # 清理臨時文件exit # 退出腳本elseecho -n "第$i次猜測,加油。"# 提供大小提示[ $num -gt ${random_num} ] && echo "太大了,往小猜。" || echo "太小了,往大猜。"fielseecho "請輸入一個介于1-50之間的數字。"fi
done
腳本后臺運行與管理
后臺運行方法
在實際工作中,可能需要讓腳本在后臺持續運行:
- 使用
&
符號:sh /server/scripts/while_01.sh &
- 使用 nohup 命令:
nohup /server/scripts/uptime.sh &
- 使用 screen 會話:
screen -S session_name
然后執行腳本
進程管理命令
sh whilel.sh &
:后臺運行腳本ctl+c
:停止當前任務ctl+z
:暫停當前任務bg
:將任務放到后臺運行fg
:將任務調到前臺運行jobs
:查看當前任務kill
:終止指定任務
并發控制示例
示例:讓所有CPU滿負荷工作
#!/bin/bash
# 獲取CPU核心數量
cpu_count=$(lscpu|grep '^CPU(s)'|awk '{print $2}')
i=1
while ((i<=${cpu_count}))
do{while : # 無限循環do((1+1)) # 簡單計算消耗CPUdone} & # 放到后臺運行((i++))
done
注意事項:
{ command1; command2; ... } &
可以將多個命令放到后臺運行{}
內部兩側需要有空格- 最后一個命令后需要有分號
使用wait等待后臺任務完成
#!/bin/bash
> /tmp/sleep # 清空文件
i=1
while [ $i -le 10 ]
do# 每個任務睡眠i秒后寫入文件( sleep $i && echo sleep $i >> /tmp/sleep )&((i++))
done
wait # 等待所有后臺任務完成
cat /tmp/sleep # 顯示結果
實戰應用
示例1:監控系統負載
#!/bin/bash
while true # 無限循環
douptime # 顯示系統負載sleep 2 # 休眠2秒
done
后臺運行并記錄日志:
#!/bin/bash
while true
douptime >> /tmp/loadaverage.log # 追加到日志文件sleep 2
done# 后臺運行
bash while2.sh &
示例2:服務監控與自動重啟
while格式:
#!/bin/bash
while true
do # 檢查sshd服務是否活躍systemctl is-active sshd.service &>/dev/nullif [ $? -ne 0 ];then # 如果服務不活躍systemctl restart sshd.service &>/dev/null # 重啟服務fisleep 5 # 每5秒檢查一次
done
until格式:
#!/bin/bash
until false # 一直執行直到false(永遠不會發生)
do systemctl is-active sshd.service &>/dev/nullif [ $? -ne 0 ];then systemctl restart sshd.service &>/dev/nullfisleep 5
done
示例3:網站可用性監控
#!/bin/bash# 參數檢查
if [ $# -ne 1 ];thenecho "Usage: $0 url"exit 1
fiurl="$1"while true
do# 使用curl檢查網站可用性if curl -o /dev/null -s --connect-timeout 5 $url;thenecho "$(date): $url is ok." # 添加時間戳elseecho "$(date): $url is error."fisleep 3 # 每3秒檢查一次
done
示例4:簡易短信平臺模擬
#!/bin/bash# 初始化變量
money=0.5 # 默認金額
msg_file=/tmp/message # 消息保存文件
> $msg_file # 清空消息文件# 手機操作菜單
function print_menu () {cat << EOF
1. 查詢余額
2. 發送消息
3. 充值
4. 退出
EOF
}# 數字檢查函數
function check_digit () {expr $1 + 1 &> /dev/null && return 0 || return 1
}# 顯示余額函數
function check_money_all () {echo "余額為:$money 元。"
}# 檢查余額是否充足(每條短信0.15元)
function check_money () {# 將元轉換為分進行比較new_money=$(echo "$money*100"|bc|cut -d . -f1)if [ ${new_money} -lt 15 ];thenecho "余額不足,請充值。"return 1 # 余額不足elsereturn 0 # 余額充足fi
}# 充值函數
function chongzhi () {read -p "充值金額(單位:元):" chongzhi_moneywhile truedocheck_digit $chongzhi_moneyif [ $? -eq 0 ] && [ ${chongzhi_money} -ge 1 ];thenmoney=$( echo "($money+${chongzhi_money})"|bc) # 使用bc進行浮點計算echo "當前余額為:$money 元"return 0elseread -p "重新輸入充值金額(至少1元):" chongzhi_money fidone
}# 發送消息函數
function send_msg () {check_money # 檢查余額if [ $? -eq 0 ];then # 余額充足read -p "請輸入消息內容:" messageecho "$(date): $message" >> ${msg_file} # 保存消息帶時間戳# 計算新余額(每條消息0.15元)new_money=$(echo "scale=2;($money*100-15)" | bc |cut -d. -f1)if [ ${new_money} -ge 100 ];thenmoney=$(echo "scale=2;${new_money}/100" | bc )elsemoney=0$(echo "scale=2;${new_money}/100" | bc )fiecho "消息已發送,當前余額為:$money 元"fi
}# 主程序
while true
doprint_menuechoread -p "請輸入你的選擇:" choiceclearcase $choice in1)check_money_all;;2)send_msg;;3)chongzhi;;4)echo "感謝使用,再見!"exit;;*)echo "無效選擇,請從1、2、3、4中選擇。" ;;esacecho
done
while循環讀取文件的四種方式
以讀取 /etc/hosts
文件為例:
方式1:使用exec重定向
#!/bin/bash
exec < /etc/hosts # 將文件重定向到標準輸入
while read line
doecho $line
done
方式2:使用管道
#!/bin/bash
cat /etc/hosts | while read line
doecho $line
done
方式3:使用輸入重定向
#!/bin/bash
while read line
doecho $line
done < /etc/hosts
方式4:設置IFS分隔符
#!/bin/bash
IFS=$'\n' # 設置字段分隔符為換行符
for line in $(cat /etc/hosts)
doecho $line
done
實戰案例
案例1:防止DDoS攻擊 - Web日志分析
#!/bin/bash
logfile=$1 # 日志文件路徑參數while true
do# 提取IP并統計訪問次數awk '{print $1}' $logfile | grep -v "^$" | sort | uniq -c > /tmp/tmp.log# 處理統計結果exec < /tmp/tmp.logwhile read linedoip=$(echo $line | awk '{print $2}') # 提取IPcount=$(echo $line | awk '{print $1}') # 提取訪問次數# 如果訪問次數超過500且不在防火墻黑名單中if [ $count -gt 500 ] && [ $(iptables -L -n | grep "$ip" | wc -l) -lt 1 ];theniptables -I INPUT -s $ip -j DROP # 封禁IPecho "$(date): $ip is dropped (PV: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 3600 # 每小時檢查一次
done
案例2:防止DDoS攻擊 - 網絡連接數監控
#!/bin/bash
while true
do# 統計ESTABLISHED狀態的連接并按IP分組ss -t | grep ESTAB | awk '{print $4}' | cut -d: -f1 | sort | uniq -c > /tmp/tmp.logexec < /tmp/tmp.logwhile read linedoip=$(echo $line | awk '{print $2}')count=$(echo $line | awk '{print $1}')# 如果單個IP連接數超過100且未被封禁if [ $count -gt 100 ] && [ $(iptables -L -n | grep "$ip" | wc -l) -lt 1 ];theniptables -I INPUT -s $ip -j DROPecho "$(date): $ip is dropped (連接數: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 10 # 每10秒檢查一次
done
總結
-
while循環特點:
- 擅長執行守護進程和持續運行的應用
- 適合處理頻率小于1分鐘的循環任務
- 多數while循環可用for循環或cron定時任務替代
-
各語句使用場景:
- 條件表達式:簡短條件判斷(文件存在、字符串非空等)
- if語句:不同值數量較少的條件判斷
- for循環:常規循環處理的首選
- while循環:守護進程、無限循環(需配合sleep控制頻率)
- case語句:服務啟動腳本、固定規則字符串處理
- select語句:菜單打印(較少使用,通常用here文檔替代)
-
函數的作用:
- 使代碼邏輯更加清晰
- 減少重復代碼開發
- 提高代碼可維護性