Shell脚本——流程控制:for循环

流程控制:for循环

1、for的传统形式

for的传统形式如下:

bash 复制代码
for variable [in words]; do
       commands
done

其中,variable是在循环期间递增的变量名,words是一个可选的条目列表,各个条目会被依次赋给variable,command是在每次循环迭代时要执行的命令。

for在命令行中也可以发挥作用。我们通过一个简单的例子来演示其工作方式:

bash 复制代码
[me@linuxbox ~]$ for i in A B C D; do echo $i; done
A
B
C
D

在这个例子中,for得到了一个包含4个单词(A、B、C、D)的列表,因此该循环会执行4次。每次循环时,其中一个单词就会被赋给变量i。在循环内部,echo命令显示变量i的值。与while和until循环一样,关键字done用于结束循环。

for真正的强大之处在于创建单词列表的多种方式。例如,可以使用花括号扩展来创建:

bash 复制代码
[me@linuxbox ~]$ for i in {A..D}; do echo $i; done
A
B
C
D

也可以使用路径名扩展来创建:

bash 复制代码
[me@linuxbox ~]$ for i in distros*.txt; do echo "$i"; done
distros-by-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernums.txt
distros-names.txt
distros.txt
distros-vernums.txt
distros-versions.txt

路径名扩展提供的路径名列表清晰整齐,可以在循环中进行处理。要注意检查路径名扩展是否的确有匹配,在默认情况下,如果路径名扩展未能匹配到任何文件,则返回通配符本身(在这个例子中,就是distros*.txt)​。为了防范这种情况,我们要在脚本中加入相应的代码:

bash 复制代码
for i in distros*.txt; do
     if [[ -e "$i" ]]; then
           echo "$i"
     fi
done

通过加入文件存在测试,就能忽略失败的扩展。

另一种创建单词列表的常见方法是命令替换:

bash 复制代码
#!/bin/bash

# longest-word: 在文件中查找长字符串

while [[ -n "$1" ]]; do
      if [[ -r "$1" ]]; then
            max_word=
            max_len=0
            for i in $(strings "$1"); do
                  len="$(echo -n "$i" | wc -c)"
                  if (( len > max_len )); then
                        max_len="$len"
                        max_word="$i"
                  fi
            done
            echo "$1: '$max_word' ($max_len characters)"
      fi
      shift
done

在这个例子中,我们要查找文件中最长的字符串。如果在命令行中指定了一个或多个文件名,该程序会使用strings(包含在GNU binutils软件包内)生成每个文件中可读的文本"单词"列表。for循环依次处理各个单词,确定当前单词是否为目前最长的。当循环结束时,显示出找到的最长单词。

有一处地方要注意,不同于惯常的实践,我们这次并没有将命令替换(strings"1")放入双引号中。这是因为我们正是想要对命令替换的结果进行单词分割,以此得到单词列表。如果加上了双引号,得到的就只是一个包含了文件中所有字符串的单个单词,这可不是我们想要的。

如果忽略了for命令中可选的in words部分,则默认处理位置参数。我们来按照这种方法修改脚本longest-word:

bash 复制代码
#!/bin/bash

# longest-word2: 在文件中查找长字符串

for i; do
      if [[ -r "$i" ]]; then
            max_word=
            max_len=0
            for j in $(strings "$i"); do
                  len="$(echo -n "$j" | wc -c)"
                  if (( len > max_len )); then
                        max_len="$len"
                        max_word="$j"
                  fi
            done
            echo "$i: '$max_word' ($max_len characters)"
      fi
done

可以看出,我们将外围的while循环改成了for循环。由于忽略了for命令中的单词列表部分,因此循环转而处理位置参数。在循环内部,先前的变量i被修改成了变量j。另外,末尾的shift也用不上了。

为什么要用i

你也许已经注意到上述每个for循环示例中选用的变量都是i,这是为什么呢?其实除了沿用惯例之外,也没什么特别原因。for可以使用任何有效变量,只不过i是最常用的,然后就是j和k。

这种惯例源自Fortran。在Fortran中,以字母I、J、K、L、M开头的未声明变量自动被归类为整数,而以其他字母开头的变量被归类为实数(带小数的数字)​。这就促使程序员在需要临时变量(通常用作循环变量)的时候使用变量I、J、K,毕竟这样比较省事。

2、for的C语言形式

最近的Bash加入了另一种for形式,其与C语言中的for形式类似,很多其他编程语言也支持这种形式:

bash 复制代码
for (( expression1; expression2; expression3 )); do
      commands
done

其中,expression1、expression2、expression3都是算术表达式,commands是在每次循环时执行的命令。

就其功能而言,这种形式等价于:

bash 复制代码
(( expression1 ))
while (( expression2 )); do
     commands
     (( expression3 ))
done

expression1用于初始化循环条件,expression2用于确定循环何时结束,expression3在每次循环末尾执行。

下面是for的C语言形式的典型应用:

bash 复制代码
#!/bin/bash

# simple_counter: 演示C风格的命令

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

执行结果如下:

bash 复制代码
[me@linuxbox ~]$ simple_counter
0
1
2
3
4

在这个例子中,expression1使用0初始化变量i;expression2在i的值小于5时,允许循环继续执行;expression3在每次循环时将i的值加1。

只要需要数值序列,C语言形式的for就能派上用场。在第33章和第34章中,我们还会多次看到for的相关应用。

3、总结

学习了for命令之后,我们就可以对sys_info_page脚本进行最终的改进了。report_home_space函数目前是这样的:

bash 复制代码
report_home_space () {
      if [[ "$(id -u)" -eq 0 ]]; then
            cat <<- _EOF_
                  <h2>Home Space Utilization (All Users)</h2>
                  <pre>$(du -sh /home/*)</pre>
                  _EOF_
      else
            cat <<- _EOF_
                  <h2>Home Space Utilization ($USER)</h2>
                  <pre>$(du -sh "$HOME")</pre>
                  _EOF_
      fi
      return
}

接下来,我们要对其进行重写,以提供有关各个用户主目录的更多细节信息,包含其中的文件和子目录的总量:

bash 复制代码
report_home_space () {

      local format="%8s%10s%10s\n"
      local i dir_list total_files total_dirs total_size user_name

      if [[ "$(id -u)" -eq 0 ]]; then
            dir_list=/home/*
            user_name="All Users"
      else
            dir_list="$HOME"
            user_name="$USER"
      fi

      echo "<h2>Home Space Utilization ($user_name)</h2>"

      for i in $dir_list; do

            total_files="$(find "$i" -type f | wc -l)"
            total_dirs="$(find "$i" -type d | wc -l)"
            total_size="$(du -sh "$i" | cut -f 1)"

            echo "<h3>$i</h3>"
            echo "<pre>"
            printf "$format" "Dirs" "Files" "Size"
            printf "$format" "----" "-----" "----"
            printf "$format" "$total_dirs" "$total_files" "$total_size"
            echo "</pre>"
      done
      return
}

这次重写运用了我们到目前为止学到的知识。虽然该函数仍要测试超级用户,但不再执行if语句中的一整套操作,而是设置了一些随后用于for循环的变量。此外,我们还在函数中加入了若干局部变量并使用printf格式化部分输出。

相关推荐
QYRdata8 小时前
2026-2032年图形处理云服务器CAGR预计达18.7%
运维·服务器·服务发现
分布式存储与RustFS8 小时前
图床搬到自己的对象存储:PicGo 加 S3 插件的完整配置
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
GeW8 小时前
制造强国底座:Linux与数据库驱动的新型工业基础设施
linux
月落汀兰9 小时前
建池后类型不可修改!深度拆解 Ceph PG 放置组,副本 / 纠删码池选型,快照与命名空间避坑
运维·ceph
xiaoye-duck9 小时前
《Linux 网络编程》深入理解 IO 多路复用:select 函数详解与 Echo 服务实战
linux·网络
Tairitsu_H9 小时前
[Linux系统] 命令行参数 | 环境变量 | 程序地址空间
linux·环境变量·命令行参数·程序地址空间
雯宝9 小时前
devices_init / buses_init / classes_init / firmware_init / hypervisor_init 函数
linux
技术人生0019 小时前
VMware Workstation 安装 Ubuntu 24.04 LTS 完整教程
运维·服务器·ubuntu
智能运维指南10 小时前
从分散运维到统一运维:2026 统一运维管理体系建设路径与选型要点
运维·一体化运维·智能运维·嘉为蓝鲸
ITyunwei098710 小时前
ITIL 5 落地前,先补齐工单数据底座的 4 步
运维·企业微信