循环命令主要有三种:for(for又分两种)、while、until,另外有两个控制循环提前结束的命令:continue、break。

 

一、 for循环

for循环有两种格式,一种bash shell基本格式,一种C语言风格的形式。

1. bash shell的基本格式

for variable [in words]
do
    commands
done

for命令的强大之处在于可以通过多种方式来创建in后面的列表,例如:

  • 直接由你列出所有要循环的值
#!/bin/bash  
  
for i in f1 f2 f3 ;  
do  
echo $i is appoint ;  
done
for str in 'This is a string'
do
    echo $str
done
  • 花括号创建 for i in {A..D}
#!/bin/bash  
  
for i in {1..10}  
do  
echo $(expr $i \* 3 + 1);  
done
  • 路径名展开 for in in dir*.txt
#!/bin/bash  
  
for file in /proc/*;  
do  
echo $file is file path \! ;  
done
  • 命令替换     for i in $(seq 1 5) 或者 for i in `ls`
#!/bin/bash  
  
for i in $(seq 1 10)  
do   
echo $(expr $i \* 3 + 1);  
done
#!/bin/bash  
  
for i in `ls`;  
do   
echo $i is file name\! ;  
done
  • 从文件中读取(可更改IFS变量改变默认字段分隔符)
#!/bin/bash  

file="states"

IFS=$'\n'  #指定字段分隔符仅为\n,默认为空格、Tab与\n
for state in `cat $file`;  
do   
    echo "visit beautiful $state"  
done

 

2. C语言风格

# for (())中变量不以$开头
for (( 初始值; 循环条件; 迭代量 ))
do
    commands
done

例子

for (( i=0; i<5; i++ ))
do
    echo $i
done

也允许在初始值中使用多个变量,分别进行处理,但循环条件只能有一个

for (( i=0, j=10; i<5; i++, j-- ))
do
    echo "$i - $j"
done

 

二、 while循环

和if一样,while也是利用命令的退出状态判断,只要退出状态为0,它就执行循环体内的内容

while commands
do
    commands
done

例子

#!/bin/bash

count=1
while [ $count -lt 5 ]
do
    echo $count
    count=$((count + 1))  # 也可以写成 count=$(expr $count + 1)
done

创建多个用户

#!/bin/bash

input="users.csv"
while IFS=',' read -r userid name
do
    echo "adding $userid"
    useradd -c "$name" -m $userid
done < "$input"

来看一个复杂点的例子(here-document语法里的内容必须顶格写,否则会报错)

#!/bin/bash

DELAY=3 # 结果展示时长
while [[ $REPLY != 0 ]]; do # 当输入值不为0,清除屏幕内容,显示菜单并执行对应命令
    clear
cat <<EOF
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
EOF
    read -p "Enter selection [0-3] > "
    if [[ $REPLY =~ ^[0-3]$ ]]; then # ~是对后面的正则表达式表示匹配的意思,匹配输出1,不匹配输出0
        if [[ $REPLY == 1 ]]; then
            echo "Hostname: $HOSTNAME"
            uptime
            sleep $DELAY
        fi
        if [[ $REPLY == 2 ]]; then
            df -h
            sleep $DELAY
        fi
        if [[ $REPLY == 3 ]]; then
            if [[ $(id -u) -eq 0 ]]; then
                echo "Home Space Utilization (All Users)"
                du -sh /home/*
            else
                echo "Home Space Utilization ($USER)"
                du -sh $HOME
            fi
            sleep $DELAY
        fi
    else
        echo "Invalid entry."
        sleep $DELAY
    fi
done
echo "Program terminated."

菜单包含在 while 循环中,每次用户选择之后,能够让程序重复显示菜单。只要 REPLY 不为0,循环就会继续,菜单就能显示,用户可以重新选择。每次动作完成之后,会执行一个 sleep 命令,所以在清空屏幕和重新显示菜单之前,程序将会停顿几秒钟,为的是能够看到选项输出结果。 一旦 REPLY 等于0,则表示选择了“退出”选项,循环就会终止,程序继续执行 done 语句之后的代码。

 

三、 until循环

也是利用命令的退出状态判断,但与while相反,退出状态不为0时才执行循环体内的内容

until commands
do
    commands
done

简单例子

#!/bin/bash

count=1
until [ $count -gt 5 ]
do
    echo $count
    count=$((count + 1))
done

 

四、 嵌套循环

上面几种循环间都是可以相互嵌套的,可根据需要选择,例如:

循环处理文件数据,结合前面提到的IFS环境变量,取出/etc/passwd各字段值

#! /bin/bash

IFS.OLD=$IFS
IFS=$'\n' #外层循环使用\n分段,解析出各行
for entry in $(cat /etc/passwd)
do
  echo "Values in $entry -"
  IFS=:   #内层循环使用:分段,解析行中各字段
  for value in $entry
  do
     echo "  $value"
  done
done

 

五、 控制循环

如果一旦启动循环就只能等到完成所有迭代,有时是非常低效的,在符合某些条件后有时可以跳出循环。跳出方法有两种:break和continue。

1. break

默认是立刻结束本层循环,执行外层内容(可能是外层循环或其他命令)。

改写下前面的例子,如果用户名为oracle,跳出内层value循环,不再输出本行值,开始下一次外层entry循环

#! /bin/bash

IFS=$'\n' #外层循环使用\n分段,解析出各行
for entry in $(cat passwd)
do
  echo "Values in $entry -"
  IFS=:   #内层循环使用:分段,解析行中各字段
  for value in $entry
  do
     if [ -z $value ] #由于:之间可能存在空字符串,需要额外加个判断,否则执行==比较会报错
     then echo "  $value"
     elif [ $value == 'oracle' ]
     then
       break  #跳出内层循环
     else
     echo "  $value"
     fi
  done
done

可以看到oracle那一行没有输出具体字段值

shell脚本编程笔记(四)—— 流程控制之循环结构_oracle

其实break还可以指定要跳出哪层循环,默认为1,即当前循环。指定方式为

break n

稍微改写一下前面的脚本,改为跳出外层(即break 2)循环

#! /bin/bash

IFS=$'\n'
for entry in $(cat passwd)
do
  echo "Values in $entry -"
  IFS=:
  for value in $entry
  do
     if [ -z $value ]
     then echo "  $value"
     elif [ $value == 'oracle' ]
     then
       break 2 #修改为跳出外层循环
     else
     echo "  $value"
     fi
  done
done

shell脚本编程笔记(四)—— 流程控制之循环结构_oracle_02

可以看到只执行到输出Values in oracle那行,下面的字段输出及外层的循环都没有再执行了(若执行外层循环还会输出grid用户相关信息)

 

2. continue

默认结束本层本次循环,开始执行本层下一次循环。还是用刚才的例子:

#! /bin/bash

IFS=$'\n'
for entry in $(cat passwd)
do
  echo "Values in $entry -"
  IFS=:
  for value in $entry
  do
     if [ -z $value ]
     then echo "  $value"
     elif [ $value == 'oracle' ]
     then
       continue
     else
     echo "  $value"
     fi
  done
done

shell脚本编程笔记(四)—— 流程控制之循环结构_bash_03

可以看到没有输出oracle这个字段值,其余都正常输出了

其实continue也可以指定要继续执行哪层循环,默认为1,即当前循环。指定方式为

continue n

稍微改写一下前面的脚本,改为继续执行外层循环,即跳出本层循环(continue 2)

#! /bin/bash

IFS=$'\n'
for entry in $(cat passwd)
do
  echo "Values in $entry -"
  IFS=:
  for value in $entry
  do
     if [ -z $value ]
     then echo "  $value"
     elif [ $value == 'oracle' ]
     then
       continue 2
     else
     echo "  $value"
     fi
  done
done

shell脚本编程笔记(四)—— 流程控制之循环结构_字段_04

可以看到跟默认的break一个效果。

 

六、 循环结果处理

如果要将循环输出的结果输入文件,或者通过管道传给其他命令,应该在done后面操作。

#!/bin/bash

count=1
while [ $count -lt 5 ]
do
    echo $count
    count=$((count + 1))  # 也可以写成 count=$(expr $count + 1)
done > output.txt
#!/bin/bash  
  
for file in /proc/*;  
do  
echo $file is file path \! ;  
done | sort

 

参考

https://www.jianshu.com/p/0beca03deff1