ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Shell for 与 select 循环:从九九乘法表到批量运维

Shell for 与 select 循环:从九九乘法表到批量运维 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三个表达式分别是变量初始化i0、范围条件i100、自增自减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 ((i1;i5;i))C 语言型三表达式写法最灵活do/done循环体边界与for同行时写;dobreak/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 ((i1;i5;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 sum0 for i in {1..10} do sum$[ sum i ] done echo $(seq -s 10)$sum # 1~10 求积 #!/bin/bash sum1 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 ((i100;i200;i)) do # 除数的范围是2和本身-1的数 for ((j2;ji-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 11 1 x 22 2 x 24 1 x 33 2 x 36 3 x 39 ...绿色数字%-2d 让每格结果左对齐补足两位 1 x 99 2 x 918 3 x 927 ... 9 x 981⚠️注意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 ((i6;i9;i)) do for ((a1;a9;a)) do for ((b0;b9;b)) do e$[a*10b-i] # e是个位数 if [ $e -lt 10 ];then for ((c0;c9;c)) do f$[e*10c-i] if [ $f -lt 10 ];then for ((d0;d9;d)) do if [ $[f*10d-i] -eq 5 ];then echo ${i}x1115$[i*1115] fi done fi done fi done done donetime bash compute2.sh只跑 0.014s而「从 1000 枚举到 9999」要 0.986s。缩小枚举范围比优化单轮计算更有效。3.2 示例百钱买百鸡鸡翁一值钱五鸡母一值钱三鸡雏三值钱一百钱买百鸡问翁、母、雏各几何#!/bin/bash sum100 money100 for ((cock_num1; cock_nummoney/5; cock_num)); do for ((hen_num1; hen_nummoney/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*518*378/3205426 100 cock:8 hen:11 chick:81 8*511*381/3403327 100 cock:12 hen:4 chick:84 12*54*384/3601228 100⚠️注意内层不再枚举chick_num而是用chick_num$(( sum - cock_num - hen_num ))直接算出来把三重循环降成两重——枚举量从 20×33×33 降到 20×33。|| continue是「条件不满足就跳过本次」的惯用写法比再套一层if更干净。3.3 示例三对情侣参加婚礼3 个新郎 A、B、C3 个新娘 X、Y、Z。已知三人说的全是假话A 说他和 X 结婚X 说她未婚夫是 CC 说他和 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 示例求 aaaaaa…aaaaa例如222222222222222数字与最长位数由键盘输入。#!/bin/bash read -p 输入1-9之间一个数 m read -p 输入数字的最长位数 n sum0 for ((i1;in;i)) do # count是j位m的值每次计算完count值设置为0 count0 for ((j1;ji;j)) do count$[countm*10**(j-1)] done echo -n $count if [ $i -eq $n ];then echo -n else echo -n fi sum$[sumcount] done echo $sum输入1-9之间一个数6 输入数字的最长位数5 6 66 666 6666 66666 740703.5 示例排列组合用 1、2、3、4 能组成多少个互不相同且无重复数字的三位数#!/bin/bash sum0 echo -n 组合包括 for ((i1;i4;i)) do for ((j1;j4;j)) do for ((k1;k4;k)) do if [ $i -ne $j -a $i -ne $k -a $j -ne $k ];then echo -n $i$j$k sum$[sum1] 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 示例批量修改文件扩展名[zhbzhb-m2 ~]$ touch file-{1..3}.txt # 思路1用 sed 替换后缀 [zhbzhb-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 命令更简洁 [zhbzhb-m2 ~]$ rename .txt .jpg *txt # rename 还能删除文件名中的特定字符串 [zhbzhb-m2 ~]$ rename file- *txt [zhbzhb-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 IP172.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% rootlocalhost fi done五、for 高级实战数据库批量备份先准备 MariaDB[zhbzhb-m2 scripts]$ sudo yum install -y mariadb-server [zhbzhb-m2 scripts]$ sudo systemctl start mariadb5.1 示例分库备份# 建库 #!/bin/bash MYSQL_CMDmysql -uroot -e for db in web1 web2 do ${MYSQL_CMD} create database $db; done # 备份 #!/bin/bash MYSQL_CMDmysqldump -uroot for db in web1 web2 do ${MYSQL_CMD} $db $db.sql; done[zhbzhb-m2 scripts]$ bash dump_dbs.sh [zhbzhb-m2 scripts]$ ls *sql web1.sql web2.sql5.2 示例分表备份# 建表 #!/bin/bash MYSQL_CMDmysql -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_CMDmysqldump -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. opensslopenssl rand -base64 8含大小写字母和数字更安全3. datedate %s%N用纳秒做随机源4. 设备文件head /dev/urandom | md5sum/dev/urandom伪随机、不阻塞/dev/random真随机、熵池不足会阻塞5. UUIDcat /proc/sys/kernel/random/uuid、uuidgen通用唯一识别码6. mkpasswdmkpasswd -l 20 -d 4 -c 4 -C 4 -s 4可精细控制各类字符数量7. mktempmktemp -u | sha1sum借临时文件名做随机源# 方法1RANDOM 混入字符串再哈希范围小易猜必须加盐 [zhbzhb-m2 ~]$ echo zhb$RANDOM|md5sum |cut -c 1-8 884075a2 # 方法2openssl推荐安全 [zhbzhb-m2 ~]$ openssl rand -base64 8 uArtNyqlCc # 方法3date 纳秒 [zhbzhb-m2 ~]$ date %s%N 1670641243375230850 # 方法4/dev/urandom md5sum [zhbzhb-m2 ~]$ head /dev/urandom |md5sum c597b451b896ad5294c582ba4fce1d0f - # 方法5UUID [zhbzhb-m2 ~]$ cat /proc/sys/kernel/random/uuid 42d2d760-f865-450f-b231-1dcabd591d1c [zhbzhb-m2 ~]$ uuidgen 5bf06174-dad9-4680-bd7d-bc1d3a02e70f # 方法6mkpasswd [zhbzhb-m2 ~]$ mkpasswd -l 20 -d 4 -c 4 -C 4 -s 4 -m6]AiA2wd^zxPXe6s1 # 方法7mktemp sha1sum [zhbzhb-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最少特殊符号个数special6.2 本篇其他命令选项速查命令 / 选项含义英文原意ping -c2只发 2 个探测包count[ A -a B ]逻辑与两个条件都成立andssh -p 端口指定对端 SSH 端口portawk {print $1}打印第 1 列awk 文本处理sudo -h显示帮助helpyum -y所有询问自动答 yesyespasswd --stdin从标准输入读密码免交互standard inputsystemctl --now同时执行 start/stopnowdf -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 示例打印课程清单[zhbzhb-m2 ~]$ cat select1.sh #!/bin/bash select course in mysql python linux do echo $course done [zhbzhb-m2 ~]$ bash select1.sh 1) mysql 2) python 3) linux #? 1 mysql #? 3 linux #? ^C # 按 ctrlc 退出7.3 示例自定义提示符 case exit[zhbzhb-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[zhbzhb-m2 ~]$ bash select2.sh 1) linux 2) mysql 3) python 4) exit 选择你的课程: 1 你选择了第 1 个条目。 linux xxx7.4 示例两数计算器[zhbzhb-m2 ~]$ cat select3.sh #! /bin/bash num110 num220 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[zhbzhb-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[zhbzhb-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 [zhbzhb-m2 ~]$ curl http://localhost/ Hello World 参考资料官方文档 · 中英双语Bash 官方手册 · 循环构造for/selectGNU Coreutils · seq 手册Bash 官方手册 · 花括号展开姊妹篇Shell case 分支 whileuntil 循环从菜单脚本到守护进程、Shell 函数语法、传参与递归调用一次讲透附命令英文全称速查表命令 / 缩写英文全称 / 原意作用forfor循环关键字inin指定取值列表do/donedo / done循环体边界seqsequence生成数字序列selectselect生成菜单的循环PS3Prompt String 3select 的提示符REPLYreplyselect 输入序号变量renamerename批量重命名文件RANDOMrandom系统内置随机数变量opensslOpen Secure Sockets Layer加密工具包可生成随机数uuidgenuniversally unique identifier generate生成 UUIDmkpasswdmake password生成随机密码mktempmake temporary创建临时文件md5sum/sha1summessage digest 5 / secure hash 1 sum校验和计算mysqldumpMySQL dump数据库备份工具useradd/chageuser add / change age建用户 / 管理密码有效期pingpacket internet groper测试网络连通性dfdisk free查看磁盘使用率总结for是批处理的主力两种语法按「要不要索引」来选企业场景里for几乎总与ssh、rename、mysqldump配合把重复劳动一次做完。select是纯交互菜单配上case $REPLY就能做一键安装器。写循环前先想清楚次数有限用for无限守候用while等值分支用case——这条主线串起整个 Shell 流程控制。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进