shell编程进阶

循环执行介绍

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行次数

  • 循环次数事先已知
  • 循环次数事先未知 常见的循环的命令:for, while, until

循环 for


#CentOS7的for帮助比CentOS8全面
[root@centos7 ~]#help for
for: for NAME [in WORDS ... ] ; do COMMANDS; done
Execute commands for each member in a list.
The `for' loop executes a sequence of commands for each member in a
list of items. If `in WORDS ...;' is not present, then `in "$@"' is
assumed. For each element in WORDS, NAME is set to that element, and
the COMMANDS are executed.
Exit Status:
Returns the status of the last command executed.
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
Arithmetic for loop.
Equivalent to
(( EXP1 ))
while (( EXP2 )); do
COMMANDS
(( EXP3 ))
done
EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
omitted, it behaves as if it evaluates to 1.
Exit Status:
Returns the status of the last command executed.

image

格式1:

for NAME [in WORDS ... ] ; do COMMANDS; done

#方式1
for 变量名 in 列表;do
    循环体
done

#方式2
for 变量名 in 列表
do
    循环体
done

执行机制:

  • 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环 结束
  • 如果省略 [in WORDS … ] ,此时使用位置参数变量 in “$@” for 循环列表生成方式:
  • 直接给出列表
  • 整数列表:
{start..end}
$(seq [start [step]] end)
  • 返回列表的命令:
$(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@,$*,$# 范例:面试题,计算1+2+3+…+100 的结果
[root@centos8 ~]#sum=0;for i in {1..100};do let sum+=i;done ;echo sum=$sum
sum=5050
[root@centos8 ~]#seq -s+ 100|bc5050
5050
[root@centos8 ~]#echo {1..100}|tr ' ' +|bc
5050
[root@centos8 ~]#seq 100|paste -sd +|bc
5050

范例: 100以内的奇数之和

[root@centos8 ~]#sum=0;for i in {1..100..2};do let sum+=i;done;echo sum=$sum
sum=2500
[root@centos8 ~]#seq -s+ 1 2 100| bc
2500
[root@centos8 ~]#echo {1..100..2}|tr ' ' + | bc
2500

范例:

[root@centos8 ~]#cat /data/scripts/for_sum.sh
#!/bin/bash
sum=0
for i in $* ; do
let sum+=i
done
echo sum=$sum
[root@centos8 ~]#bash /data/scripts/for_sum.sh 1 2 3 4 5 6
sum=21

范例: 批量创建用户

[root@centos8 ~]#cat createuser.sh
#!/bin/bash
[ $# -eq 0 ] && { echo "Usage: createuser.sh USERNAME ..." ;exit 1 ; }
for user ;do
    id $user &> /dev/null && echo $user is exist || { useradd $user ; echo $user
is created; }
done

范例: 批量创建用户和并设置随机密码

[root@centos8 script]#cat user_for.sh
#!/bin/bash
for i in {1..10};do
useradd user$i
    PASS=`cat /dev/urandom | tr -dc '[:alnum:]' |head -c12`
    echo $PASS | passwd --stdin user$i &> /dev/null
    echo user$i:$PASS >> /data/user.log
    echo "user$i is created"
done

范例: 九九乘法表

[root@centos8 script]#cat 9x9_for.sh
#!/bin/bash
for i in {1..9};do
    for j in `seq $i`;do
        echo -e "${j}x${i}=$[j*i]\t\c"
    done
    echo
done

范例: 实现九九乘法表

[root@centos8 ~]#vim 9x9.sh
for j in {1..9};do
    for i in `seq $j`;do
        echo -e "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t\c"
    done
    echo
done
echo

for((i=1;i<=9;i++));do
    for((j=1;j<=i;j++));do
        printf "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t"
    done
    printf "\n"
done

范例: 倒状的九九乘法表

[root@ubuntu1804 ~]#cat 9x9_v2.sh
#!/bin/bash
for i in {1..9};do
    for j in $(seq `echo $[10-$i]`);do
        echo -ne "${j}x`echo $[10-i]`=$(((10-i)*j))\t"
    done
    echo
done
[root@ubuntu1804 ~]#bash 9x9_v2.sh
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x4=4 2x4=8 3x4=12 4x4=16
1x3=3 2x3=6 3x3=9
1x2=2 2x2=4
1x1=1

生产案例:将指定目录下的文件所有文件的后缀改名为 bak 后缀

[root@centos8 ~]#cat /data/scripts/for_rename.sh
#!/bin/bash
DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for FILE in * ;do
    PRE=`echo $FILE|grep -Eo ".*\."`
    mv $FILE ${PRE}bak
# PRE=`echo $FILE|rev|cut -d. -f 2-|rev`
# PRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'
# SUFFIX=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\2/p'`
# mv $FILE $PRE.bak
done

范例:M37期面试题,要求将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下

#1.创建YYYY-MM-DD格式的目录,当前日期一年前365天到目前共365个目录,里面有10个文件.log后缀的
文件
[root@centos8 ~]#cat for_dir.sh
#!/bin/bash
PDIR=/data/test
for i in {1..365};do
    DIR=`date -d "-$i day" +%F`
    mkdir -pv $PDIR/$DIR
    cd $PDIR/$DIR
    for j in {1..10};do
        touch $RANDOM.log
    done
done
#2.将上面的目录移动到YYYY-MM/DD/下
#!/bin/bash
#
DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for subdir in * ;do
    YYYY_MM=`echo $subdir |cut -d"-" -f1,2`
    DD=`echo $subdir |cut -d"-" -f3`
    [ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
    mv $subdir/* $YYYY_MM/$DD
done
rm -rf $DIR/*-*-*

面试题:扫描一个网段:10.0.0.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来

[root@centos8 ~]#cat /data/scripts/for_scan_host.sh
#!/bin/bash
NET=10.0.0
for ID in {1..254};do
    {
        ping -c1 -W1 $NET.$ID &> /dev/null && echo $NET.$ID is up| tee -a
host_list.log || echo $NET.$ID is down
    }&
done
wait

格式2

双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格 的变量操作 I=10;((I++)) image

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
    循环体
done

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断 范例: image image image image image

练习:用 for 实现

1、判断/var/目录下所有文件的类型
2、添加10个用户user1-user10,密码为8位随机字符
3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为
文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start 4、编写脚本,提示输入正整数n的值,计算1+2+…+n的总和
5、计算100以内所有能被3整除的整数之和
6、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态
7、打印九九乘法表
8、在/testdir目录下创建10个html文件,文件名格式为数字N(从1到10)加随机8个字母,如: 1AbCdeFgH.html
9、打印等腰三角形
10、猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个。第二天早上又将剩下的桃子 吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,只 剩下一个桃子了。求第一天共摘了多少?

循环 while

格式:

while COMMANDS; do COMMANDS; done

while CONDITION; do
    循环体
done

说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件 为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:CONDTION一般应该有循环 控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为 true
退出条件:CONDITION为 false
无限循环

while true; do
    循环体
done

while : ; do
    循环体
done

image image

练习:用while实现

1、编写脚本,求100以内所有正奇数之和
2、编写脚本,提示请输入网络地址,如:192.168.0.0,判断输入的网段中主机在线状态,并统计在线和 离线主机各多少
3、编写脚本,打印九九乘法表
4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值
5、编写脚本,实现打印国际象棋棋盘
6、后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、 ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut -c1-10 后的结果,请破解这些字符串对应的RANDOM值

while 特殊用法 while read

while 循环的特殊用法,遍历文件或文本的每一行
格式:

while read line; do
    循环体
done < /PATH/FROM/SOMEFILE

说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
范例:

[root@centos8 ~]#echo magedu | read X ; echo $X
[root@centos8 ~]#echo magedu | while read X ; do echo $X;done
magedu
[root@centos8 ~]#echo magedu | { read X ; echo $X; }
magedu
[root@centos8 ~]#echo magedu | ( read X ; echo $X )
magedu
[root@centos8 ~]#echo mage wang zhang | ( read X Y Z; echo $X $Y $Z )
mage wang zhang
[root@centos8 ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang

范例:

cat while_read_diskcheck.sh
#!/bin/bash
WARNING=80
MAIL=root@wangxiaochun.com
df |sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p"|while read DEVICE
USE;do
    if [ $USE -gt $WARNING ] ;then
        echo "$DEVICE will be full,use:$USE" | mail -s "DISK WARNING" $MAIL
    fi
done

范例:

cat while_read_check_dos.sh
#!/bin/bash
MAX=3
lastb | sed -rn '/ssh:/s@.* ([0-9.]{1,3}{3}[0-9]{1,3}) .*@\1@p'|sort |uniq -c
|while read count ip ;do
    if [ $count -gt $MAX ];then
        iptables -A INPUT -s $ip -j REJECT
    fi
done

范例:查看/sbin/nologin的shell类型的用户名和UID

cat while_read_passwd.sh
#!/bin/bash
while read line ;do
        if [[ "$line" =~ /sbin/nologin$ ]] ;then
                echo $line | cut -d: -f1,3
        fi
done < /etc/passwd