Bash 循环

Bash 循环

Bash 提供三种循环语法 forwhileuntil

for 的语法是 for var in list;do command; donefor (( expression1; expression2; expression3 )) ; do command; done

whileuntil 的语法都是 xxx condition;do command; done,xxx 可替换为 whileuntil

if 的语法是 if condition;then command;fi

case 的语法是 case expression in ... esac

这很明显是两种命令风格。

while 循环

while 循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。

while condition; do
  commands
done

上面代码中,只要满足条件 condition,就会执行命令 commands。然后,再次判断是否满足条件 condition,只要满足,就会一直执行下去。只有不满足条件,才会退出循环。

循环条件 condition 可以使用 test 命令,跟 if 结构的判断条件写法一致。

关于 if 名,请参考《Bash 条件判断》的 判断表达式 小节

关键字 do 可以跟 while 不在同一行,这时两者之间不需要使用分号分隔。一般都会写在同一行

创建脚本 while_loop.sh

#!/usr/bin/env bash

num=10
while (( num > 0 ));do
    echo nnmber is $num
    (( num-- ))
done

赋值后执行

$ ./while_loop.sh
nnmber is 10
nnmber is 9
nnmber is 8
nnmber is 7
nnmber is 6
nnmber is 5
nnmber is 4
nnmber is 3
nnmber is 2
nnmber is 1

如果 while 的条件为 true,例如 while true;do echo "knock knock";sleep 1;done,则循环体会无限循环,可以按下 Ctrl + c 停止。

while 的条件部分可以为任意数量的命令,这些命令都会执行,但是整个判断条件执行结果的真伪只看最后一个命令的执行结果。

$ while true; false; do echo 'Hi, looping ...'; done

上面代码运行后,不会有任何输出,因为 while 的最后一个命令是 false

这在学习 if 命令的时候,就了解过这个结论,具体请看《Bash 条件判断》

所以循环体也可以写成这样

#!/usr/bin/env bash

num=10
while (( num-- ));(( num > 0 ));do
    echo nnmber is $num
done

赋权并输出

$ ./while_loop.sh
nnmber is 9
nnmber is 8
nnmber is 7
nnmber is 6
nnmber is 5
nnmber is 4
nnmber is 3
nnmber is 2
nnmber is 1

until 循环

until 循环与 while 循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。

while 循环就一字之差,特性上完全一直。

until condition; do
  commands
done

关键字 do 可以与 until 不写在同一行,这时两者之间不需要分号分隔。

until condition
do
  commands
done

简单实践如下

创建脚本 until_loop.sh

#!/usr/bin/env bash

num=10
until  (( num--  )); (( num < 0 )) ;do
    echo nnmber is $num
done

赋权后执行

$ ./until_loop.sh
nnmber is 9
nnmber is 8
nnmber is 7
nnmber is 6
nnmber is 5
nnmber is 4
nnmber is 3
nnmber is 2
nnmber is 1
nnmber is 0

until 的条件部分也可以是一个命令,表示在这个命令执行成功之前,不断重复尝试。

until cp $1 $2; do
  echo 'Attempt to copy failed. waiting...'
  sleep 5
done

上面例子表示,只要 cp $1 $2 这个命令执行不成功,就 5 秒钟后再尝试一次,直到成功为止。

until 循环都可以转为 while 循环,只要把条件设为否定即可。上面这个例子可以改写如下。

while ! cp $1 $2; do
  echo 'Attempt to copy failed. waiting...'
  sleep 5
done

一般来说,until 用得比较少,完全可以统一都使用 while

for...in 循环

for...in 循环用于遍历列表的每一项。

for variable in list
do
  commands
done

关键词 do 可以跟 for 写在同一行,两者使用分号分隔。

for variable in list; do
  commands
done

上面语法中,for 循环会依次从 list 列表中取出一项,作为变量 variable,然后在循环体中进行处理。

下面是一个例子。

#!/bin/bash

for i in word1 word2 word3; do
  echo $i
done

上面例子中,word1 word2 word3 是一个包含三个单词的列表,in 后面用逗号分隔的多个项,就是列表

变量 i 依次等于 word1word2word3,命令 echo $i 则会相应地执行三次。

列表可以由通配符产生。

for i in *.png; do
  ls -l $i
done

上面例子中,*.png 会替换成当前目录中所有 PNG 图片文件,变量 i 会依次等于每一个文件。

for 循环经常跟大括号拓展(例如 {1..3})配合使用

具体请看《Bash 的模式拓展》的 大括号扩展 小节

例如

$ for num in {1..10};do echo number is $num ;done
number is 1
number is 2
number is 3
number is 4
number is 5
number is 6
number is 7
number is 8
number is 9
number is 10
$ for num in {1..10..4};do echo number is $num ;done
number is 1
number is 5
number is 9
$ for num in {10..1..3};do echo number is $num ;done
number is 10
number is 7
number is 4
number is 1

列表也可以通过子命令产生。比如我们可以通过 cat 命令查看文本文件中所有的单词,比如

文件 /opt/shell_script/text.txt

aaa bbb cccc
dddd eee fff ggg hhhh iiii
1111 2222 333
Bash 真的 很好用,非常推荐

读取文件

$ for word in $(cat /opt/shell_script/text.txt);do echo $word;done
aaa
bbb
cccc
dddd
eee
fff
ggg
hhhh
iiii
1111
2222
333
Bash
真的
很好用,非常推荐

我们之前在《Bash read 命令》中学习过通过 read 命令读取文件。

我们在脚本中经常利用 for...in 循环来遍历脚本的所有参数 for param in "$@";do done,同时 in list 的部分可以省略,这时 list 默认等于脚本的所有参数 $@。但是,为了可读性,最好还是不要省略,参考下面的例子。

for filename; do
  echo "$filename"
done

# 等同于

for filename in "$@" ; do
  echo "$filename"
done

在函数体中也是一样的,for...in 循环省略 in list 的部分,则 list 默认等于函数的所有参数。但是最好不要省略。

for 循环

for 循环还支持 C 语言的循环语法。

for (( expression1; expression2; expression3 )); do
  commands
done

上面代码中,expression1 用来初始化循环条件,expression2 用来决定循环结束的条件,expression3 在每次循环迭代的末尾执行,用于更新值。

注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号 $

它等同于下面的 while 循环。

(( expression1 ))
while (( expression2 )); do
  commands
  (( expression3 ))
done

for 条件部分的三个语句,都可以省略。

for ((;;))
do
  read var
  if [ "$var" = "." ]; then
    break
  fi
done

上面脚本会反复读取命令行输入,直到用户输入了一个点(.)为止,才会跳出循环。

简单实践如下:

$ for ((i=0;i<10;i++ ));do echo number is $i;done
number is 0
number is 1
number is 2
number is 3
number is 4
number is 5
number is 6
number is 7
number is 8
number is 9

使用起来跟 Java 中的 for 循环一样,非常好用。

break,continue

Bash 提供了两个内部命令 breakcontinue,用来在循环内部跳出循环。

简单实验如下:

创建脚本 ./break_continue.sh

#!/usr/bin/env bash

for index in {1..10};do
    if [ $index -eq 3 ];then
        continue
    elif [ $index -eq 5 ];then
        break
    fi
    echo $index
done

赋权后执行

$ ./break_continue.sh
1
2
4

select 结构

用来做功能脚本的菜单非常好用

select 结构主要用来生成简单的菜单。它的语法与 for...in 循环基本一致。

select name [in list] ;do
  commands
done

select 和 do 也可以写成两行,这样就不需要用分号隔开

select name
[in list]
do
  commands
done

Bash 会对 select 依次进行下面的处理。

  1. select 生成一个菜单,内容是列表 list 的每一项,并且每一项前面还有一个数字编号。
  2. Bash 提示用户选择一项,输入它的编号。
  3. 用户输入以后,Bash 会将该项的内容存在变量 name,该项的编号存入环境变量 REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。
  4. 执行命令体 commandscommands 中可以使用 break 跳出循环。
  5. 执行结束后,回到第一步,重复这个过程。直到用户按下 Ctrl + c,退出执行。

通常 commands 部分是一个 case 命令,针对不同项,执行不同的命令。

输入提示符由变量 PS3 控制,默认是 #? ,我们可以对此进行自定义,比如 PS3="please input menu num > "

关于 PS3 以及相关提示符的介绍,请看《Bash 命令提示符》

简单实践如下:

创建 select.sh

#!/usr/bin/env bash

echo "select function"

PS3="please input menu num > "

data=()

select func in query add delete exit_fun ; do
    case $func in
         query)
           echo ${data[@]}
         ;;
         add)
           next_val=${#data[@]}
           data[$next_val]="$((++next_val))"
           echo ${data[@]}
         ;;
         delete)
           last_index=0
           for val_index in ${!data[@]};do
               last_index=val_index
           done
           unset data[$last_index]
           echo ${data[@]}
         ;;
         exit_fun)
            break
         ;;
    esac
done

赋权后执行:

$ ./select.sh
select function
1) query
2) add
3) delete
4) exit_fun
please input menu num > 1

please input menu num > 2
1
please input menu num > 2
1 2
please input menu num > 2
1 2 3
please input menu num > 2
1 2 3 4
please input menu num > 3
1 2 3
please input menu num > 3
1 2
please input menu num > 3
1
please input menu num > 3

please input menu num > 4

此外,我们可以将一个数组变量放到 list 的位置,动态生成菜单。