for是 Shell 里用得最多的循环——批量改文件名、批量备份、批量重启十台主机、扫一遍内网在线机器,全靠它。这篇把for的两种语法、十个经典算法题、六类企业批处理案例讲全,最后用select收尾,让你两分钟写出一键安装菜单。<br /><br />✍️ by zhb
本文概要:
| 项目 | 内容 |
|---|---|
| 文章主题 | Shellfor循环(取值型 / C 语言型)与select菜单循环 |
| 所属类别 | Linux 系统运维 / Shell 编程基础 |
| 知识关键字 | for/in/do/done、seq、rename、嵌套循环、随机数生成、select/PS3/REPLY |
本文脉络:先讲for的两种语法(取值型与 C 语言型);再用六个基础示例和五个经典算法题练手;接着是企业批处理六例(改扩展名、批量启服务、扫内网、监控磁盘、分库备份、批量建账号);然后是 Linux 生成随机数的 7 种方法;最后讲select菜单循环与四个案例。文中命令名均标注英文全称,完整清单见文末《附:命令英文全称速查表》。
一、for 循环的两种语法
for主要用于执行次数有限的循环,不适合守护进程或无限循环。
1.1 变量取值型
for 变量名 in 变量取值列表 do 指令... donein 变量取值列表可以省略,省略时相当于in "$@",即for i等价于for i in "$@"。
执行过程:变量名依次取列表里的值(空格分隔),每取一个就执行一轮循环体,执行到done结束本轮,再取下一个;取完最后一个值并执行完即结束。
1.2 C 语言型
for ((exp1;exp2;exp3)) do 指令... done三个表达式分别是变量初始化(i=0)、范围条件(i<100)、自增自减(i++)。初始值满足范围条件就进入循环,不满足就退出——比while更规范工整。
1.3 语法速查表
| 关键字 / 写法 | 含义 | 说明 |
|---|---|---|
for 变量 in 列表 | 取值型循环 | 逐个取列表中的值 |
for i | 省略in | 等价于for i in "$@",遍历脚本参数 |
{1..5} | 花括号展开 | 生成 1~5 序列,{5..1}为逆序 |
$(seq 5) | 命令替换生成序列 | seq 5 -1 1支持步长 |
for ((i=1;i<=5;i++)) | C 语言型 | 三表达式写法,最灵活 |
do/done | 循环体边界 | 与for同行时写;do |
break/continue | 跳出 / 跳过 | 嵌套循环中可用break 2跳出两层 |
二、for 基础实践
2.1 示例:竖向升序与降序打印
# 升序:列表型 / seq 型 / C 语言型三种写法 for i in {1..5}; do echo $i; done for i in $(seq 5); do echo $i; done for ((i=1;i<=5;i++)); do echo $i; done # 降序:花括号逆序 / seq 带步长 for i in {5..1}; do echo $i; done for i in $(seq 5 -1 1); do echo $i; done2.3 示例:求和与求乘积
# 1~10 求和 #!/bin/bash sum=0 for i in {1..10} do sum=$[ sum + i ] done echo $(seq -s '+' 10)=$sum # 1~10 求积 #!/bin/bash sum=1 for i in {1..10} do ((sum*=i)) done echo $(seq -s '*' 10)=$sum2.4 示例:求水仙花数
水仙花数:一个 3 位数,其每一位数字的 3 次幂之和等于它本身。
#!/bin/bash for num in {100..999} do n1=$[num/100] n2=$[num%100/10] n3=$[num%10] if [ $[ n1**3 + n2**3 + n3**3 ] -eq $num ];then echo $n1^3 + $n2^3 + $n3^3 = $n1$n2$n3 fi done2.5 示例:求素数
素数:除 1 和它本身外,不能被任何整数整除的数。
#!/bin/bash echo -n "100~200之间所有的素数为:" for ((i=100;i<=200;i++)) do # 除数的范围是2和本身-1的数 for ((j=2;j<=i-1;j++)) do # 如果能整除,说明不是质数,跳出本次循环 if [ $[i%j] -eq 0 ];then break fi # 如果本次循环到最后都不能整除,则是质数 if [ $j -eq $[i-1] ];then echo -n " $i" fi done done echo100~200之间所有的素数为: 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 1992.6 示例:九九乘法表
#!/bin/bash for i in {1..9}; do for j in $(seq 1 $i); do printf "\033[32m%d x %d=%-2d\033[0m " "$j" "$i" $((i*j)) done echo done1 x 1=1 1 x 2=2 2 x 2=4 1 x 3=3 2 x 3=6 3 x 3=9 ...(绿色数字,`%-2d` 让每格结果左对齐补足两位) 1 x 9=9 2 x 9=18 3 x 9=27 ... 9 x 9=81⚠️注意:
printf "\033[32m...\033[0m"用\033[32m(绿色)与\033[0m(复位)把每格结果包起来,比echo -e更适合对齐输出——%-2d保证两位数占满两格,乘号两侧不会因结果位数差而错位。
三、经典算法题
3.1 示例:求出满足条件的 4 位数
题目:一个四位数除以一个一位数,商各位都是 1,余数为 5。
解题关键:余数是 5,则除数至少为 6;除数≥6 的个位数乘某数仍是一位数,说明商各位都是 1。
解法:分别对每一位枚举(比从 1000 枚举到 9999 快 70 倍)
#!/bin/bash for ((i=6;i<=9;i++)) do for ((a=1;a<=9;a++)) do for ((b=0;b<=9;b++)) do e=$[a*10+b-i] # e是个位数 if [ $e -lt 10 ];then for ((c=0;c<=9;c++)) do f=$[e*10+c-i] if [ $f -lt 10 ];then for ((d=0;d<=9;d++)) do if [ $[f*10+d-i] -eq 5 ];then echo "${i}x111+5=$[i*111+5]" fi done fi done fi done done donetime bash compute2.sh只跑 0.014s;而「从 1000 枚举到 9999」要 0.986s。缩小枚举范围比优化单轮计算更有效。
3.2 示例:百钱买百鸡
鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一;百钱买百鸡,问翁、母、雏各几何?
#!/bin/bash sum=100 money=100 for ((cock_num=1; cock_num<=money/5; cock_num++)); do for ((hen_num=1; hen_num<=money/3; hen_num++)); do chick_num=$(( sum - cock_num - hen_num )) # 雏鸡数必须为正,且能被 3 整除(三只一钱) (( chick_num > 0 && chick_num % 3 == 0 )) || continue if (( cock_num*5 + hen_num*3 + chick_num/3 == money )); then echo "cock:$cock_num hen:$hen_num chick:$chick_num" echo "$cock_num*5+$hen_num*3+$chick_num/3=$(($cock_num*5))+$(($hen_num*3))+$(($chick_num/3))= 100" echo fi done donecock:4 hen:18 chick:78 4*5+18*3+78/3=20+54+26= 100 cock:8 hen:11 chick:81 8*5+11*3+81/3=40+33+27= 100 cock:12 hen:4 chick:84 12*5+4*3+84/3=60+12+28= 100⚠️注意:内层不再枚举
chick_num,而是用chick_num=$(( sum - cock_num - hen_num ))直接算出来,把三重循环降成两重——枚举量从 20×33×33 降到 20×33。|| continue是「条件不满足就跳过本次」的惯用写法,比再套一层if更干净。
3.3 示例:三对情侣参加婚礼
3 个新郎 A、B、C,3 个新娘 X、Y、Z。已知三人说的全是假话:A 说他和 X 结婚;X 说她未婚夫是 C;C 说他和 Z 结婚。问谁和谁结婚?
#!/bin/bash for A in X Y Z do for B in X Y Z do for C in X Y Z do # 三方不能同娶一人;且三人说的都是假话,取反 if [ "$A" != "$B" -a "$A" != "$C" -a "$B" != "$C" -a "$A" != "X" -a "$C" != "X" -a "$C" != "Z" ];then echo A 与 $A 结婚 echo B 与 $B 结婚 echo C 与 $C 结婚 fi done done doneA 与 Z 结婚 B 与 X 结婚 C 与 Y 结婚3.4 示例:求 a+aa+aaa+…+aaaaa
例如2+22+222+2222+22222,数字与最长位数由键盘输入。
#!/bin/bash read -p "输入1-9之间一个数:" m read -p "输入数字的最长位数:" n sum=0 for ((i=1;i<=n;i++)) do # count是j位m的值,每次计算完count值设置为0 count=0 for ((j=1;j<=i;j++)) do count=$[count+m*10**(j-1)] done echo -n "$count " if [ $i -eq $n ];then echo -n "= " else echo -n "+ " fi sum=$[sum+count] done echo $sum输入1-9之间一个数:6 输入数字的最长位数:5 6 + 66 + 666 + 6666 + 66666 = 740703.5 示例:排列组合
用 1、2、3、4 能组成多少个互不相同且无重复数字的三位数?
#!/bin/bash sum=0 echo -n "组合包括:" for ((i=1;i<=4;i++)) do for ((j=1;j<=4;j++)) do for ((k=1;k<=4;k++)) do if [ $i -ne $j -a $i -ne $k -a $j -ne $k ];then echo -n "$i$j$k " sum=$[sum+1] fi done done done echo echo "组合总数量为:$sum"组合包括:123 124 132 134 142 143 213 214 231 234 241 243 312 314 321 324 341 342 412 413 421 423 431 432 组合总数量为:24四、企业级批处理案例
4.1 示例:批量修改文件扩展名
[zhb@zhb-m2 ~]$ touch file-{1..3}.txt # 思路1:用 sed 替换后缀 [zhb@zhb-m2 ~]$ cat for6.sh #!/bin/bash for file in $(ls *.txt) do new_name=$(echo $file|sed 's/.txt$/.jpg/') mv $file ${new_name} done # 思路2:直接用 rename 命令(更简洁) [zhb@zhb-m2 ~]$ rename ".txt" ".jpg" *txt # rename 还能删除文件名中的特定字符串 [zhb@zhb-m2 ~]$ rename "file-" "" *txt [zhb@zhb-m2 ~]$ ls *txt 1.txt 2.txt 3.txt4.2 示例:批量设置服务开机启动
#!/bin/bash for service in crond atd chronyd do systemctl enable $service done4.3 示例:批量重启多台主机 sshd
#!/bin/bash for host in server{1..10} do ssh root@$host systemctl restart sshd done4.4 示例:批量备份文件
#!/bin/bash for file in host* do cp $file /backup/${file}.bak; done4.5 示例:扫描内网在线主机
#!/bin/sh for i in `seq 1 254` do IP=172.25.254.$i ping -c2 $IP &> /dev/null if [ $? -eq 0 ];then echo "$IP is online" >> node_online.txt else echo "$IP is offline" >> node_offline.txt fi done想并发扫描,只需在
fi后加&。
4.6 示例:监控一组主机磁盘
#!/bin/bash hostfile=/tmp/hosts if [ $(cat $hostfile | wc -l ) -lt 1 ] ;then echo $hostfile is empty. exit 1 fi for host in $(cat $hostfile) do > disk.log echo "=============HOSTNAME: $host =============" >> disk.log ssh $host df -h |grep '^/dev/'|awk '{print $1,$5}'|cut -f 1 -d%|awk '{if ($2 > 80) print $0}' >> disk.log if [ $(cat disk.log | wc -l) -gt 1 ];then cat disk.log |mail -s "$host: disk is greater than 80%" root@localhost fi done五、for 高级实战:数据库批量备份
先准备 MariaDB:
[zhb@zhb-m2 scripts]$ sudo yum install -y mariadb-server [zhb@zhb-m2 scripts]$ sudo systemctl start mariadb5.1 示例:分库备份
# 建库 #!/bin/bash MYSQL_CMD='mysql -uroot -e ' for db in web1 web2 do ${MYSQL_CMD} "create database $db;" done # 备份 #!/bin/bash MYSQL_CMD='mysqldump -uroot ' for db in web1 web2 do ${MYSQL_CMD} $db > $db.sql; done[zhb@zhb-m2 scripts]$ bash dump_dbs.sh [zhb@zhb-m2 scripts]$ ls *sql web1.sql web2.sql5.2 示例:分表备份
# 建表 #!/bin/bash MYSQL_CMD='mysql -uroot $db -e ' for db in web1 web2 do ${MYSQL_CMD} "create table test(id int,name varchar(16));insert into test values(1,'testdata')"; done # 备份表 #!/bin/bash MYSQL_CMD='mysqldump -uroot $db' for db in web1 web2 do ${MYSQL_CMD} > ${db}_test.sql; done5.3 示例:批量创建系统账号
需求:清单来自/tmp/users;密码为随机字符数字混合;要求首登改密;用户信息存入/etc/users;成功显示Add USERNAME Success。
cat > /tmp/users <<EOF zhb01 zhb02 tony jack EOF#!/bin/bash # Name: create_users.sh # 引入系统函数库 . /etc/init.d/functions users_info_file=/etc/users users_list_file=/tmp/users [ ! -f $users_info_file ] && touch $users_info_file exec < ${users_list_file} # 创建用户并设置密码 while read username do # 获取随机密码(用户名加随机数再哈希,截取 9 位) pass=$(echo "$username+$RANDOM" | md5sum | cut -c 3-11) # 添加用户、强制首登改密、设置密码 useradd $username &>/dev/null && chage -d 0 $username && \ echo "$pass" | passwd --stdin $username &>/dev/null && \ echo -e "username: $username \t password:$pass" >> $users_info_file if [ $? -eq 0 ];then action "$username is ok" /bin/true else action "$username is fail" /bin/false fi done echo --------------------------- cat $users_info_file⚠️注意:
chage -d 0 $username把「上次改密日期」设为 0,用户下次登录会被强制改密——这是新账号交付的标准动作。别用passwd -e替代后就以为一样,两者触发时机在部分发行版上略有差异。
六、Linux 生成随机数的 7 种方法
| 方法 | 命令 | 特点 |
|---|---|---|
| 1. 环境变量 | echo "zhb$RANDOM" | md5sum | cut -c 1-8 | 最简单,RANDOM范围 0~32767,需混入字符串增强 |
| 2. openssl | openssl rand -base64 8 | 含大小写字母和数字,更安全 |
| 3. date | date +%s%N | 用纳秒做随机源 |
| 4. 设备文件 | head /dev/urandom | md5sum | /dev/urandom伪随机、不阻塞;/dev/random真随机、熵池不足会阻塞 |
| 5. UUID | cat /proc/sys/kernel/random/uuid、uuidgen | 通用唯一识别码 |
| 6. mkpasswd | mkpasswd -l 20 -d 4 -c 4 -C 4 -s 4 | 可精细控制各类字符数量 |
| 7. mktemp | mktemp -u | sha1sum | 借临时文件名做随机源 |
# 方法1:RANDOM 混入字符串再哈希(范围小易猜,必须加盐) [zhb@zhb-m2 ~]$ echo "zhb$RANDOM"|md5sum |cut -c 1-8 884075a2 # 方法2:openssl(推荐,安全) [zhb@zhb-m2 ~]$ openssl rand -base64 8 +uArtNyqlCc= # 方法3:date 纳秒 [zhb@zhb-m2 ~]$ date +%s%N 1670641243375230850 # 方法4:/dev/urandom + md5sum [zhb@zhb-m2 ~]$ head /dev/urandom |md5sum c597b451b896ad5294c582ba4fce1d0f - # 方法5:UUID [zhb@zhb-m2 ~]$ cat /proc/sys/kernel/random/uuid 42d2d760-f865-450f-b231-1dcabd591d1c [zhb@zhb-m2 ~]$ uuidgen 5bf06174-dad9-4680-bd7d-bc1d3a02e70f # 方法6:mkpasswd [zhb@zhb-m2 ~]$ mkpasswd -l 20 -d 4 -c 4 -C 4 -s 4 -m6]Ai+A2wd^zxPXe6s1 # 方法7:mktemp + sha1sum [zhb@zhb-m2 ~]$ mktemp -u |sha1sum 9505b8634ad25c9db571a4beb62d49fc6484e06c -6.1 mkpasswd 参数速查表
| 参数 | 含义 | 英文原意 |
|---|---|---|
mkpasswd | 生成随机密码 | make password |
-l n | 密码总长度 | length |
-d n | 最少数字个数 | digits |
-c n | 最少小写字母个数 | lower case |
-C n | 最少大写字母个数 | upper Case |
-s n | 最少特殊符号个数 | special |
6.2 本篇其他命令选项速查
| 命令 / 选项 | 含义 | 英文原意 |
|---|---|---|
ping -c2 | 只发 2 个探测包 | count |
[ A -a B ] | 逻辑与,两个条件都成立 | and |
ssh -p 端口 | 指定对端 SSH 端口 | port |
awk '{print $1}' | 打印第 1 列 | awk 文本处理 |
sudo -h | 显示帮助 | help |
yum -y | 所有询问自动答 yes | yes |
passwd --stdin | 从标准输入读密码(免交互) | standard input |
systemctl --now | 同时执行 start/stop | now |
df -h | 以人类可读单位显示磁盘占用 | human-readable |
七、select 循环与菜单
select的主要作用是创建菜单:运行时按数字顺序列出菜单项,显示提示符(默认#?),等待用户输入序号。
select 变量名 [ in 菜单取值列表 ] do 指令... donein 菜单取值列表可省略,省略时相当于in "$@"。要点:
- 每次执行时,只等用户输入序号,每输一次执行一次循环(不会自动遍历完整个列表)。
select是没有终止的循环,除非某个分支执行了exit/break;否则会一直跑下去。- 遍历顺序完全取决于用户选择,这与
for/while/until按顺序遍历不同。
7.1 select 环境变量速查表
| 变量 | 含义 | 说明 |
|---|---|---|
PS3 | 选择提示符 | 默认#?,可自定义如选择你的课程: |
REPLY | 用户输入的序号 | 可直接在case $REPLY中使用 |
COLUMNS | 菜单显示列数 | 影响菜单项的排列 |
7.2 示例:打印课程清单
[zhb@zhb-m2 ~]$ cat select1.sh #!/bin/bash select course in mysql python linux do echo $course done [zhb@zhb-m2 ~]$ bash select1.sh 1) mysql 2) python 3) linux #? 1 mysql #? 3 linux #? ^C # 按 ctrl+c 退出7.3 示例:自定义提示符 + case + exit
[zhb@zhb-m2 ~]$ cat select2.sh #!/bin/bash PS3="选择你的课程: " select course in linux mysql python exit do echo "你选择了第 $REPLY 个条目。" case $course in linux) echo linux xxx ;; mysql) echo mysql yyy ;; python) echo python zzz ;; exit) exit ;; esac echo done[zhb@zhb-m2 ~]$ bash select2.sh 1) linux 2) mysql 3) python 4) exit 选择你的课程: 1 你选择了第 1 个条目。 linux xxx7.4 示例:两数计算器
[zhb@zhb-m2 ~]$ cat select3.sh #! /bin/bash num1=10 num2=20 echo There are two numbers $num2 and $num1 。 select method in 加法 减法 乘法 退出 do case $method in 加法) echo $[ num1 + num2 ] ;; 减法) echo $[ num2 - num1 ] ;; 乘法) echo $[ num1 * num2 ] ;; 退出) exit ;; *) echo 从1-4中选择 ;; esac done7.5 示例:一键安装 httpd 与 mysql
[zhb@zhb-m2 ~]$ cat install_httpd_or_mysql.sh #! /bin/bash function print () { if [ "$1" == "PASS" ];then echo -e '\033[1;32mPASS\033[0;39m' elif [ "$1" == "FAIL" ];then echo -e '\033[1;31mFAIL\033[0;39m' elif [ "$1" == "DONE" ];then echo -e '\033[1;35mDONE\033[0;39m' else echo "Usage: print PASS|FAIL|DONE" fi } function InstallService () { case $1 in httpd) echo -n "Start installing httpd..." { yum install -y httpd systemctl enable httpd --now echo Hello World > /var/www/html/index.html } &>/dev/null [ $? -eq 0 ] && print DONE || print FAIL ;; mysql) echo -n "Start installing mysql..." { yum install -y mariadb-server systemctl enable mariadb --now } &>/dev/null [ $? -eq 0 ] && print DONE || print FAIL ;; esac } select app in "Install httpd" "Install mysql" "Exit" do case $REPLY in 1) InstallService httpd ;; 2) InstallService mysql ;; 3) exit ;; *) echo 从1-3中选择。 ;; esac done[zhb@zhb-m2 ~]$ sudo bash install_httpd_or_mysql.sh 1) Install httpd 2) Install mysql 3) Exit #? 1 Start installing httpd...DONE #? 2 Start installing mysql...DONE #? 3 [zhb@zhb-m2 ~]$ curl http://localhost/ Hello World📚 参考资料(官方文档 · 中英双语)
- Bash 官方手册 · 循环构造(for/select)
- GNU Coreutils · seq 手册
- Bash 官方手册 · 花括号展开
- 姊妹篇:Shell case 分支 + while/until 循环:从菜单脚本到守护进程、Shell 函数:语法、传参与递归调用一次讲透
附:命令英文全称速查表
| 命令 / 缩写 | 英文全称 / 原意 | 作用 |
|---|---|---|
for | for | 循环关键字 |
in | in | 指定取值列表 |
do/done | do / done | 循环体边界 |
seq | sequence | 生成数字序列 |
select | select | 生成菜单的循环 |
PS3 | Prompt String 3 | select 的提示符 |
REPLY | reply | select 输入序号变量 |
rename | rename | 批量重命名文件 |
RANDOM | random | 系统内置随机数变量 |
openssl | Open Secure Sockets Layer | 加密工具包(可生成随机数) |
uuidgen | universally unique identifier generate | 生成 UUID |
mkpasswd | make password | 生成随机密码 |
mktemp | make temporary | 创建临时文件 |
md5sum/sha1sum | message digest 5 / secure hash 1 sum | 校验和计算 |
mysqldump | MySQL dump | 数据库备份工具 |
useradd/chage | user add / change age | 建用户 / 管理密码有效期 |
ping | packet internet groper | 测试网络连通性 |
df | disk free | 查看磁盘使用率 |
总结:
for是批处理的主力,两种语法按「要不要索引」来选;企业场景里for几乎总与ssh、rename、mysqldump配合,把重复劳动一次做完。select是纯交互菜单,配上case $REPLY就能做一键安装器。写循环前先想清楚:次数有限用for,无限守候用while,等值分支用case——这条主线串起整个 Shell 流程控制。