wiki
  • Introduction
  • Ansible
    • 1 初识Ansible
    • 2 Ansible Inventory 配置详解
    • 3 Ansible Ad-hoc 命令集
    • 3 Ansible lineinfile 模块详解
    • 4 Ansible Playbook
      • 4.1 Playbook的结构及handler用法
      • 4.2 Playbook循环语句
      • 4.3 Playbook条件语句
      • 4.4 Playbook高级用法
      • 4.5 Playbook之tags
      • 4.6 巧用Roles
      • 4.7 文件管理模块及Jinja2过滤器
      • 4.8 yaml 语法
    • 5 Ansible变量
      • 5.1 自定义变量
      • 5.2 Fact变量
      • 5.3 魔法变量
      • 5.4 使用lookup生成变量
      • 5.5 使用vault配置加密
    • 6 Ansible使用优化
  • Linux
    • linux简介
    • 文件管理
      • 查看文件类型
      • 查看文件详细属性
      • 创建、复制、移动、删除
      • 查看文件内容
      • 打包、压缩
    • 文件权限
      • 基本权限UGO
    • 计划任务
    • 用户管理
    • 进程管理
    • 管道及I/O重定向
    • 软件包管理
    • 网络管理
    • 日志logrotate
    • /etc/security/limits.conf 详解与配置
    • 常用知识
  • Shell
    • 第一部分 初见shell
      • 1. 为什么使用shell编程
      • 2. 和Sha-Bang(#!)一起出发
        • 2.1 调用一个脚本
        • 2.2 牛刀小试
    • 第二部分 shell基础
      • 3. 特殊字符
      • 4. 变量与参数
        • 4.1 变量替换
        • 4.2 变量赋值
        • 4.3 Bash弱类型变量
        • 4.4 特殊变量类型
      • 5. 引用
        • 5.1 引用变量
        • 5.2 转义
      • 6. 退出与退出状态
      • 7. 测试
        • 7.1 测试结构
        • 7.2 文件测试操作
        • 7.3 其他比较操作
        • 7.4 嵌套 if/then 条件测试
        • 7.5 牛刀小试
      • 8. 运算符相关话题
        • 8.1 运算符
        • 8.2 数字常量
        • 8.3 双圆括号结构
        • 8.4 运算符优先级
    • 第三部分 shell进阶
      • 9. 换个角度看变量
        • 9.1 内部变量
        • 9.2 变量类型标注:declare 与 typeset
          • 9.2.1 declare 的另类用法
        • 9.3 $RANDOM:生成随机数
      • 10. 变量处理
        • 10.1 字符串处理
          • 10.1.1 使用 awk 处理字符串
          • 10.1.2 参考资料
        • 10.2 参数替换
      • 11. 循环与分支
        • 11.1 循环
        • 11.2 嵌套循环
        • 11.3 循环控制
        • 11.4 测试与分支
      • 12. 命令替换
      • 13. 算术扩展
    • 第四部分 命令
    • 第五部分 高级话题
      • 18.正则表达式
        • 18.1正则表达式简介
        • 18.2文件名替换
        • 18.3 正则表达式对照表
      • 19. 嵌入文档
      • 20. I/O 重定向
        • 20.1 使用 exec
        • 20.2 重定向代码块
        • 20.3 应用程序
      • 21. 子shell
      • 22. 限制模式的Shell
      • 23. 进程替换
      • 24. 函数
        • 24.1 复杂函数和函数复杂性
        • 24.2 局部变量
        • 24.3 不适用局部变量的递归
      • 25. 别名
      • 26. 列表结构
      • 27. 数组
      • 30. 网络编程
      • 33. 选项
      • 34. 陷阱
      • 36. 其他
        • 36.5 有颜色的脚本
        • 36.11 ssh远程操作
    • 第六部分 Google Shell 风格指南
    • 第七部分 实例
  • 3 Python
    • 3.1 简介
    • 3.2 数据类型
    • python subprocess模块的使用
    • python解析nginx配置文件
    • python调用jenkinsAPI批量拷贝视图job
    • python调用zabbixAPI批量添加web监控
    • python监控activemq集群
  • 4 Golang
    • 4.1 程序结构
    • 4.2 数据类型
      • 4.2.1 基础数据类型
      • 4.2.2 复合数据类型
    • 4.3 函数
  • Docker
    • Docker文章推荐
    • Docker Harbor
    • Docker Harbor HA
    • 快速搭建rabbitmq高可用集群
  • Nginx
    • 1.初识Nginx
    • 2.nginx架构基础
    • 3.nginx配置文件详解
      • 3.1 nginx全局块和events块配置
      • 3.2 nginxHTTP块配置
      • 3.3 nginxSERVER块配置
      • 3.4 nginxLOCATION块配置
      • 3.5 nginx常用模块
    • 4.内核参数优化
    • 5.状态码
    • 6.nginx的常见问题
      • Nginx出现403 forbidden的三种原因
  • 8 Tomcat
    • 8.1 tomcat简介
    • 生产环境中的tomcat配置
    • tomcat假死现象
    • Tomcat 错误代号及状态码
    • tomcat注意事项
  • 9 Elasticsearch
    • 9.1 概述
      • 9.1.1 Elasticsearch 简介及其发展历史
      • 9.1.2 Elastic Stack 家族成员及其应用场景
    • 9.2 安装上手
      • 9.2.1 Elasticsearch的安装和简单配置
      • 9.2.2 Kibana的安装和界面快速浏览
      • 9.2.3 在Docker容器中运行Elasticsearch,Kibana和Cerebro
    • 9.3 Elasticsearch入门
      • 9.3.1 基本概念
      • 9.3.2 文档的基本操作CRUP与批量操作
      • 9.3.3 倒排索引入门
      • 9.3.4 通过分析器进行分词
      • 9.3.5 Search API概览
      • 9.3.6 URI Search详解
      • 9.3.7 Request Body和Query DSL简介
  • 10 Zabbix
    • 10.1 zabbix4.4新功能
    • 10.2 zabbix借助ansible安装
    • 10.3 zabbix添加报警媒介
    • 10.4 MySQL监控模块
    • 10.5 Redis监控模块
    • 10.6 常见问题
    • 10.7 用zabbix api批量添加web监控
    • 10.8 activemq监控
    • 10.9 zookeeper集群监控
    • 10.10 Todolist
  • 11 消息中间件
    • 11.1 activemq集群
    • 11.2 rabbitmq
  • 12 常见集群的搭建
    • 12.1 keepalived和heartbeat对比
    • 12.2 zookeeper集群
    • 12.3 fastdfs
    • 12.4 rocketmq集群的搭建
  • 13 常见服务的搭建
    • none
    • 13.2 jdk
    • 13.3 python3.6
    • 13.4 新建博客方法
    • 13.5 confluence
  • 14 Markdown
    • 14.1 让Markdown写作更简单Typora完全使用指南
  • 16 Mysql
    • 16.1 MySQL基本操作
    • 16.2 列表类型和数据完整性
    • 16.3 数据库设计与查询语句
    • 16.4 视图、事务、索引、函数
    • 16.5 MySQL存储引擎
    • 16.6 用户权限和访问控制
    • 16.7 备份恢复
    • 16.8 AB复制
    • 16.9 主主和keepalived
    • 16.10 读写分离和Amoeba
    • 16.11 MySQL逻辑架构
    • 16.12 MySQL锁等待
    • 16.13 MySQL常见问题
    • 16.14 MySQL bin-log日志清理
  • 17 Redis
    • 17.1 安装
    • 17.2 配置
    • 17.3 服务器端和客户端命令
    • 17.4 数据库操作
      • 17.4.1 string类型
      • 17.4.2 键命令
      • 17.4.3 hash类型
      • 17.4.4 list类型
      • 17.4.5 set类型
      • 17.4.6 zset类型
    • 17.5 centos7安装redis-5.0.0主从
    • 17.6 redis5.0.4集群搭建步骤
    • 17.7 centos7安装升级ruby
    • 17.8 redis-dump方式导入导出数据
    • redis面试题
  • 18 HTML
    • 18.1 html入门
  • 19 CSS
    • 19.1 列表、div、span
    • 19.2 整体感知、常见属性、选择器
    • 19.3 css性质、盒模型、标准文档流、浮动
    • 19.4 行高和字号、超链接美化、background
    • 19.5 相对定位、绝对定位、固定定位、z-index
  • 20 jenkins
    • 20.1 jenkins的安装配置
    • 20.2 Jenkins Job迁移
    • Jenkins常见问题
    • Jenkins关闭和重启的实现方式
    • 使用Jenkins Pipeline自动化构建发布
  • 21 vim
    • vim设置参数以方便YAML语句录入
    • vim常用命令
  • 22 网络
    • TCP协议三次握手四次挥手
    • ip地址
  • 23 未分类
    • DEV SIT UAT PET SIM PRD PROD常见环境英文缩写含义
    • 调优
    • 编程语言的分类
    • curl方式执行shell脚本时如何传参
    • 中文技术文档写作规范
    • PrometheusAlert
  • 24 常见问题
    • centos时区
    • curl https报错
    • du-df磁盘使用不一致
    • linux下增加交换分区
    • mysql最大连接数
  • 25 openldap
    • Centos7 搭建openldap
    • phpldapadmin操作指导
  • 26 gitlab
    • gitlab备份迁移升级&集成openldap
  • 27 监控体系
    • 聊头监控体系
    • grafana问题
  • git
    • git的基本使用
    • Commit message 和 Change log 编写指南
Powered by GitBook
On this page
  • for 循环
  • for arg in [list]
  • while 循环
  • until

Was this helpful?

  1. Shell
  2. 第三部分 shell进阶
  3. 11. 循环与分支

11.1 循环

Previous11. 循环与分支Next11.2 嵌套循环

Last updated 5 years ago

Was this helpful?

循环是当循环控制条件为真时,一系列命令迭代执行的代码块。

for 循环

for arg in [list]

这是 shell 中最基本的循环结构,它与C语言形式的循环有着明显的不同。

for arg in [list]
do
  command(s)...
done

在循环的过程中,arg 会从 list 中连续获得每一个变量的值。

for arg in "$var1" "$var2" "$var3" ... "$varN"
# 第一次循环中,arg = $var1
# 第二次循环中,arg = $var2
# 第三次循环中,arg = $var3
# ...
# 第 N 次循环中,arg = $varN

# 为了防止可能的字符分割问题,[list] 中的参数都需要被引用。

如果 do 和 for 写在同一行时,需要在 list 之后加上一个分号。

for arg in [list] ; do

样例 11-1. 简单的 for 循环

#!/bin/bash
# 列出太阳系的所有行星。

for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
  echo $planet  # 每一行输出一个行星。
done

echo; echo

for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
    # 所有的行星都输出在一行上。
    # 整个 'list' 被包裹在引号中时是作为一个单一的变量。
    # 为什么?因为空格也是变量的一部分。
do
  echo $planet
done

echo; echo "Whoops! Pluto is no longer a planet!"

exit 0

样例 11-2. for 循环 [list] 中的每一个变量有两个参数的情况

#!/bin/bash
# 让行星再躺次枪。

# 将每个行星与其到太阳的距离放在一起。

for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
do
  set -- $planet  #  解析变量 "planet"
                  #+ 并将其每个部分赋值给位置参数。
  # "--" 防止一些极端情况,比如 $planet 为空或者以破折号开头。

  # 因为位置参数会被覆盖掉,因此需要先保存原先的位置参数。
  # 你可以使用数组来保存
  #         original_params=("$@")

  echo "$1        $2,000,000 miles from the sum"
  #-------两个制表符---将后面的一系列 0 连到参数 $2 上。
done

# (感谢 S.C. 做出的额外注释。)

exit 0

一个单一变量也可以成为 for 循环中的 [list]。

样例 11-3. 文件信息:查看一个单一变量中含有的文件列表的文件信息

#!/bin/bash
# fileinfo.sh

FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind"     # 你可能会感兴趣的一系列文件。
                  # 包含一个不存在的文件,/usr/bin/fakefile。

echo

for file in $FILES
do

  if [ ! -e "$file" ]       # 检查文件是否存在。
  then
    echo "$file does not exist."; echo
    continue                # 继续判断下一个文件。
  fi

  ls -l $file | awk '{ print $8 "         file size: " $5 }'  # 输出其中的两个域。
  whatis `basename $file`   # 文件信息。
  # 脚本正常运行需要注意提前设置好 whatis 的数据。
  # 使用 root 权限运行 /usr/bin/makewhatis 可以完成。
  echo
done

exit 0

for 循环中的 [list] 可以是一个参数。

样例 11-4. 操作含有一系列文件的参数

#!/bin/bash

filename="*txt"

for file in $filename
do
 echo "Contents of $file"
 echo "---"
 cat "$file"
 echo
done

如果在匹配文件扩展名的 for 循环中的 [list] 含有通配符(* 和 ?),那么将会进行文件名扩展。

样例 11-5. 在 for 循环中操作文件

#!/bin/bash
# list-glob.sh: 通过文件名扩展在 for 循环中产生 [list]。
# 通配 = 文件名扩展。

echo

for file in *
#           ^  Bash 在检测到通配表达式时,
#+             会进行文件名扩展。
do
  ls -l "$file"  # 列出 $PWD(当前工作目录)下的所有文件。
  #  回忆一下,通配符 "*" 会匹配所有的文件名,
  #+ 但是,在文件名扩展中,他将不会匹配以点开头的文件。

  #  如果没有匹配到文件,那么它将会扩展为它自身。
  #  为了防止出现这种情况,需要设置 nullglob 选项。
  #+    (shopt -s nullglob)。
  #  感谢 S.C.
done

echo; echo

for file in [jx]*
do
  rm -f $file    # 删除当前目录下所有以 "j" 或 "x" 开头的文件。
  echo "Removed file \"$file\"".
done

echo

exit 0

样例 11-6. 缺少 in [list] 的 for 循环

#!/bin/bash

# 尝试在带参数和不带参数两种情况下调用这个脚本,观察发生了什么。

for a
do
 echo -n "$a "
done

#  缺失 'in list' 的情况下,循环会遍历 '$@'
#+(命令行参数列表,包括空格)。

echo

exit 0

样例 11-7. 在 for 循环中使用命令代换生成 [list]

#!/bin/bash
# for-loopcmd.sh: 带命令代换所生成 [list] 的 for 循环

NUMBERS="9 7 3 8 37.53"

for number in `echo $NUMBERS`  # for number in 9 7 3 8 37.53
do
  echo -n "$number "
done

echo
exit 0

下面是使用命令代换生成 [list] 的更加复杂的例子。

样例 11-8. 一种替代 grep 搜索二进制文件的方法

#!/bin/bash
# bin-grep.sh: 在二进制文件中定位匹配的字符串。

# 一种替代 `grep` 搜索二进制文件的方法
# 与 "grep -a" 的效果类似

E_BADARGS=65
E_NOFILE=66

if [ $# -ne 2 ]
then
  echo "Usage: `basename $0` search_string filename"
  exit $E_BADARGS
fi

if [ ! -f "$2" ]
then
  echo "File \"$2\" does not exist."
  exit $E_NOFILE
fi


IFS=$'\012'       # 按照 Anton Filippov 的意见应该是
                  # IFS="\n"
for word in $( strings "$2" | grep "$1" )
# "strings" 命令列出二进制文件中的所有字符串。
# 将结果通过管道输出到 "grep" 中,检查是不是匹配的字符串。
do
  echo $word
done

# 就像 S.C. 指出的那样,第 23-30 行可以换成下面的形式:
#    strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'


# 尝试运行脚本 "./bin-grep.sh mem /bin/ls"

exit 0

下面的例子同样展示了如何使用命令代换生成 [list]。

样例 11-9. 列出系统中的所有用户

#!/bin/bash
# userlist.sh

PASSWORD_FILE=/etc/passwd
n=1           # 用户数量

for name in $(awk 'BEGIN{fs=":"}{print $1}' < "$PASSWORD_FILE" )
# 分隔符 = :              ^^^^^^
# 输出第一个域                    ^^^^^^^^
# 读取密码文件 /etc/passwd                    ^^^^^^^^^^^^^^^^^
do
  echo "USER #$n = $name"
  let "n += 1"
done


# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #33 = bozo

exit $?

# 讨论:
# -----
# 一个普通用户是如何读取 /etc/passwd 文件的?
# 提示:检查 /etc/passwd 的文件权限。
# 这算不算是一个安全漏洞?为什么?

另外一个关于 [list] 的例子也来自于命令代换。

样例 11-10. 检查目录中所有二进制文件的原作者

#!/bin/bash
# findstring.sh
# 在指定目录的二进制文件中寻找指定的字符串。

directory=/usr/bin
fstring="Free Software Foundation"  # 查看哪些文件来自于 FSF。

for file in $( find $directory -type f -name '*' | sort )
do
  strings -f $file | grep "$fstring" | sed -e "s%$driectory%%"
  #  在 "sed" 表达式中,你需要替换掉 "/" 分隔符,
  #+ 因为 "/" 是一个会被过滤的字符。
  #  如果不做替换,将会产生一个错误。(你可以尝试一下。)
done

exit $?

# 简单的练习:
# ----------
# 修改脚本,使其可以从命令行参数中获取 $directory 和 $fstring。
generate_list ()
{
  echo "one two three"
}

for word in $(generate_list)  # "word" 获得函数执行的结果。
do
  echo "$word"
done

# one
# two
# three

for 循环的结果可以通过管道导向至一个或多个命令中。

样例 11-11. 列出目录中的所有符号链接。

#!/bin/bash
# symlinks.sh: 列出目录中的所有符号链接。

directory=${1-`pwd`}
# 如果没有特别指定,缺省目录为当前工作目录。
# 等价于下面的代码块。
# ---------------------------------------------------
# ARGS=1                 # 只有一个命令行参数。
#
# if [ $# -ne "$ARGS" ]  # 如果不是只有一个参数的情况下
# then
#   directory=`pwd`      # 设为当前工作目录。
# else
#   directory=$1
# fi
# ---------------------------------------------------

echo "symbolic links in directory \"$directory\""

for file in "$( find $directory -type 1 )"   # -type 1 = 符号链接
do
  echo "$file"
done | sort                                  # 否则文件顺序会是乱序。
#  严格的来说这里并不需要使用循环,
#+ 因为 "find" 命令的输出结果已经被扩展成一个单一字符串了。
#  然而,为了方便大家理解,我们使用了循环的方式。

#  Dominik 'Aeneas' Schnitzer 指出,
#+ 不引用 $( find $directory -type 1 ) 的话,
#  脚本将在文件名包含空格时阻塞。

exit 0


# --------------------------------------------------------
# Jean Helou 提供了另外一种方法:

echo "symbolic links in directory \"$directory\""
# 备份当前的内部字段分隔符。谨慎永远没有坏处。
OLDIFS=$IFS
IFS=:

for file in $(find $directory -type 1 -printf "%p$IFS")
do     #                              ^^^^^^^^^^^^^^^^
       echo "$file"
       done|sort

# James "Mike" Conley 建议将 Helou 的代码修改为:

OLDIFS=$IFS
IFS='' # 空的内部字段分隔符意味着将不会分隔任何字符串
for file in $( find $directory -type 1 )
do
  echo $file
  done | sort

#  上面的代码可以在目录名包含冒号(前一个允许包含空格)
#+ 的情况下仍旧正常工作。

样例 11-12. 将目录中的所有符号链接保存到文件中。

#!/bin/bash
# symlinks.sh: 列出目录中的所有符号链接。

OUTFILE=symlinks.list

directory=${1-`pwd`}
# 如果没有特别指定,缺省目录为当前工作目录。


echo "symbolic links in directory \"$directory\"" > "$OUTFILE"
echo "---------------------------" >> "$OUTFILE"

for file in "$( find $directory -type 1 )"    # -type 1 = 符号链接
do
  echo "$file"
done | sort >> "$OUTFILE"                     # 将 stdout 的循环结果
#           ^^^^^^^^^^^^^                       重定向到文件。

# echo "Output file = $OUTFILE"

exit $?

样例 11-13. C语言风格的循环

#!/bin/bash
# 用多种方式数到10。

echo

# 基础版
for a in 1 2 3 4 5 6 7 8 9 10
do
  echo -n "$a "
done

echo; echo

# +==========================================+

# 使用 "seq"
for a in `seq 10`
do
  echo -n "$a "
done

echo; echo

# +==========================================+

# 使用大括号扩展语法
# Bash 3+ 版本有效。
for a in {1..10}
do
  echo -n "$a "
done

echo; echo

# +==========================================+

# 现在用类似C语言的语法再实现一次。

LIMIT=10

for ((a=1; a <= LIMIT ; a++))  # 双圆括号语法,不带 $ 的 LIMIT
do
  echo -n "$a "
done                           # 从 ksh93 中学习到的特性。

echo; echo

# +==========================================+

# 我们现在使用C语言中的逗号运算符来使得两个变量同时增加。

for ((a=1, b=1; a <= LIMIT ; a++, b++))
do  # 逗号连接操作。
  echo -n "$a-$b "
done

echo; echo

exit 0

---

接下来,我们将展示在真实环境中应用的循环。

样例 11-14. 在批处理模式下使用 efax

#!/bin/bash
# 传真(必须提前安装了 'efax' 模块)。

EXPECTED_ARGS=2
E_BADARGS=85
MODEM_PORT="/dev/ttyS2"   # 你的电脑可能会不一样。
#                ^^^^^       PCMCIA 调制解调卡缺省端口。

if [ $# -ne $EXPECTED_ARGS ]
# 检查是不是传入了适当数量的命令行参数。
then
   echo "Usage: `basename $0` phone# text-file"
   exit $E_BADARGS
fi


if [ ! -f "$2" ]
then
  echo "File $2 is not a text file."
  #     File 不是一个正常文件或者文件不存在。
  exit $E_BADARGS
fi


fax make $2              # 根据文本文件创建传真格式文件。

for file in $(ls $2.0*)  # 连接转换后的文件。
                         # 在参数列表中使用通配符(文件名通配)。
do
  fil="$fil $file"
done

efax -d "$MODEM_PORT"  -t "T$1" $fil   # 最后使用 efax。
# 如果上面一行执行失败,尝试添加 -o1。


#  S.C. 指出,上面的 for 循环可以被压缩为
#     efax -d /dev/ttyS2 -o1 -t "T$1" $2.0*
#+ 但是这并不是一个好主意。

exit $?   # efax 同时也会将诊断信息传递给标准输出。
for((n=1; n<=10; n++))
# 没有 do!
{
  echo -n "* $n *"
}
# 没有 done!


# 输出:
# * 1 ** 2 ** 3 ** 4 ** 5 ** 6 ** 7 ** 8 ** 9 ** 10 *
# 并且 echo $? 返回 0,因此 Bash 并不认为这是一个错误。


echo


#  但是注意在典型的 for 循环 for n in [list] ... 中,
#+ 需要在结尾加一个分号。

for n in 1 2 3
{  echo -n "$n "; }
#               ^


# 感谢 Yongye 指出这一点。

while 循环

while [ condition ]
do
  command(s)...
done

就像在 for 循环中那样,将 do 和循环条件放在同一行时需要加一个分号。

while [ condition ] ; do

样例 11-15. 简单的 while 循环

#!/bin/bash

var0=0
LIMIT=10

while [ "$var0" -lt "$LIMIT" ]
#      ^                    ^
# 必须有空格,因为这是测试结构
do
  echo -n "$var0 "        # -n 不会另起一行
  #             ^           空格用来分开输出的数字。

  var0=`expr $var0 + 1`   # var0=$(($var0+1))  效果相同。
                          # var0=$((var0 + 1)) 效果相同。
                          # let "var0 += 1"    效果相同。
done                      # 还有许多其他的方法也可以达到相同的效果。

echo

exit 0

样例 11-16. 另一个例子

#!/bin/bash

echo
                               # 等价于:
while [ "$var1" != "end" ]     # while test "$var1" != "end"
do
  echo "Input variable #1 (end to exit) "
  read var1                    # 不是 'read $var1' (为什么?)。
  echo "variable #1 = $var1"   # 因为存在 "#",所以需要使用引号。
  # 如果输入的是 "end",也将会在这里输出。
  # 在结束本轮循环之前都不会再测试循环条件了。
  echo
done

exit 0

一个 while 循环可以有多个测试条件,但只有最后的那一个条件决定了循环是否终止。这是一种你需要注意到的不同于其他循环的语法。

样例 11-17. 多条件 while 循环

#!/bin/bash

var1=unset
previous=$var1

while echo "previous-variable = $previous"
      echo
      previous=$var1
      [ "$var1" != end ] # 记录下 $var1 之前的值。
      # 在 while 循环中有4个条件,但只有最后的那个控制循环。
      # 最后一个条件的退出状态才会被记录。
do
echo "Input variable #1 (end to exit) "
  read var1
  echo "variable #1 = $var1"
done

# 猜猜这是怎样实现的。
# 这是一个很小的技巧。

exit 0

样例 11-18. C语言风格的 while 循环

#!/bin/bash
# wh-loopc.sh: 在 "while" 循环中计数到10。

LIMIT=10                 # 循环10次。
a=1

while [ "$a" -le $LIMIT ]
do
  echo -n "$a "
  let "a+=1"
done                     # 没什么好奇怪的吧。

echo; echo

# +==============================================+

# 现在我们用C语言风格再写一次。

((a = 1))      # a=1
# 双圆括号结构允许像C语言一样在赋值语句中使用空格。

while (( a <= LIMIT ))   #  双圆括号结构,
do                       #+ 并且没有使用 "$"。
  echo -n "$a "
  ((a += 1))             # let "a+=1"
  # 是的,就是这样。
  # 双圆括号结构允许像C语言一样自增一个变量。
done

echo

# 这可以让C和Java程序猿感觉更加舒服。

exit 0
t=0

condition ()
{
  ((t++))

  if [ $t -lt 5 ]
  then
    return 0  # true 真
  else
    return 1  # false 假
  fi
}

while condition
#     ^^^^^^^^^
#     调用函数循环四次。
do
  echo "Still going: t = $t"
done

# Still going: t = 1
# Still going: t = 2
# Still going: t = 3
# Still going: t = 4
while condition
do
  command(s) ...
done
cat $filename |    # 从文件获得输入。
while read line    # 只要还有可以读入的行,循环就继续。
do
  ...
done

# ==================== 摘自样例脚本 "sd.sh" =================== #

  while read value   # 一次读入一个数据。
  do
    rt=$(echo "scale=$SC; $rt + $value" | bc)
    (( ct++ ))
  done

  am=$(echo "scale=$SC; $rt / $ct" | bc)

  echo $am; return $ct   # 这个功能“返回”了2个值。
  # 注意:这个技巧在 $ct > 255 的情况下会失效。
  # 如果要操作更大的数字,注释掉上面的 "return $ct" 就可以了。
} <"$datafile"   # 传入数据文件。

until

与 while 循环相反,until 循环测试其顶部的循环条件,直到其中的条件为真时停止。

until [ condition-is-true ]
do
  commands(s)...
done

注意到,跟其他的一些编程语言不同,until 循环的测试条件在循环顶部。

就像在 for 循环中那样,将 do 和循环条件放在同一行时需要加一个分号。

until[ condition-is-true ] ; do

样例 11-19. until 循环

#!/bin/bash

END_CONDITION=end

until [ "$var1" = "$END_CONDITION" ]
# 在循环顶部测试条件。
do
  echo "Input variable #1 "
  echo "($END_CONDITION to exit)"
  read var1
  echo "variable #1 = $var1"
  echo
done

#                ---                   #

#  就像 "for" 和 "while" 循环一样,
#+ "until" 循环也可以写的像C语言一样。

LIMIT=10
var=0

until (( var > LIMIT ))
do  # ^^ ^     ^     ^^   没有方括号,没有 $ 前缀。
  echo -n "$var "
  (( var++ ))
done    # 0 1 2 3 4 5 6 7 8 9 10


exit 0

如何在 for,while 和 until 之间做出选择?我们知道在C语言中,在已知循环次数的情况下更加倾向于使用 for 循环。但是在Bash中情况可能更加复杂一些。Bash中的 for 循环相比起其他语言来说,结构更加松散,使用更加灵活。因此使用你认为最简单的就好。

参数 list 中允许含有 。

[list] 中的每一个元素中都可能含有多个参数。这在处理参数组中非常有用。在这种情况下,使用 命令(查看 )强制解析 [list] 中的每一个元素,并将元素的每一个部分分配给位置参数。

如果在 for 循环中省略 in [list] 部分,那么循环将会遍历位置参数($@)。 中使用到了这一点。也可以查看 。

可以在 for 循环中使用 生成 [list]。查看 , 和 。

最后一个关于 [list] 和命令代换的例子,但这个例子中的命令是一个。

只需要对上一个样例做一些小小的改动,就可以把在标准输出 stdout 中的循环 到文件中。

还有另外一种看起来非常像C语言中循环那样的语法。你需要使用到 语法。

还可以查看 , 和 。

do 和 done 圈定了 for 循环代码块的范围。但是在一些特殊的情况下,也可以被 取代。

while 循环结构会在循环顶部检测循环条件,若循环条件为真( 为0)则循环持续进行。与 不同的是,while 循环是在不知道循环次数的情况下使用的。

在 while 循环结构中,你不仅可以使用像 if/test 中那样的 ,也可以使用用途更广泛的 (while [[ condition ]])。

在 while 循环中,括号结构 。比如说 。

就像 for 循环一样, while 循环也可以使用双圆括号结构写得像C语言那样(也可以查看)。

在测试部分,while 循环可以调用 。

和 结构一样,while 循环也可以省略括号。

在 while 循环中结合 命令,我们就得到了一个非常易于使用的 结构。它可以用来读取和解析文件。

在 while 循环后面可以通过 < 将标准输入 中。 while 循环同样可以 传入标准输入中。

通配符
set
样例 15-16
样例 A-15
样例 15-17
命令代换
样例 16-54
样例 11-11
样例 16-48
函数
重定向
双圆括号
样例 27-16
样例 27-17
样例 A-6
退出状态
for 循环
括号结构
双括号结构
并不是必须存在的
getopts 结构
样例 8-5
函数
if 测试
read
while read
关键字
大括号
重定位到文件
通过管道
note
note
note