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格式化部分输出。

相关推荐
星恒随风5 小时前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql
唐墨1239 小时前
linux kernel 比较数据大小的宏定义
linux·算法
程序开发阿元10 小时前
密码不进工具:一个独立开发者的服务器凭据管理办法
linux·ssh
分布式存储与RustFS12 小时前
群晖 NAS 被 C3RB3R 锁了:本地备份一起被加密,RustFS 对象锁为什么删不掉
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
真好啊又活了一天12 小时前
服务器和虚机配置IPV6
运维·服务器·github
zixu_star13 小时前
定时任务是什么?从日常自动化到 AI 应用,软件如何自动执行任务?
运维·人工智能·自动化
AR-26710-13 小时前
Linux Day33——定时任务+lvm
linux·运维
墨痕诉清风14 小时前
SSRF漏洞实战记录+OSS桶命令执行+MCP漏洞
linux·服务器·数据库
byte轻骑兵14 小时前
实战 4:BlueZ 源码中添加自定义设备过滤规则
linux·物联网·bluez·电脑蓝牙·嵌入式蓝牙
润滑卫士14 小时前
液压系统工作粘度窗口等级选型:泵允许粘度范围与油温-粘度匹配计算
运维