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

相关推荐
可乐鸡翅yeah_1 小时前
MPEG‑TS 分片 PTS/DTS 时间戳异常排错,HLS 音画不同步定位实战
运维·测试用例·音视频·媒体·m3u8
事圆则缓1 小时前
Ubuntu 安装与配置 Samba 服务器
linux·服务器·ubuntu
GeW1 小时前
每天2小时,21天闭环计划-RHCE通关时间表全公开
linux
SkyWalking中文站2 小时前
Horizon UI 1.0 正式发布:SkyWalking 新一代控制台接棒 Booster
运维·监控·自动化运维
weixin_450813532 小时前
pdf文档转为markdown文档
linux·pdf
随便做点啥2 小时前
32卡64G-910B4-16后端集群部署报告
java·大数据·运维
我的小卷呀3 小时前
Linux ip 命令底层逻辑与实战指南
linux·运维·c语言·网络协议·tcp/ip·网络安全
CingSyuan3 小时前
4U GPU服务器整机无法上电故障排查实录:通过交叉替换、逐项回装与跨槽位测试,定位RTX 4080显卡故障及硬盘槽位异常并完成临时恢复
运维·服务器
handler013 小时前
【Linux】ELF、链接与动态库,程序装载全解析
linux·运维·服务器·chrome·c·elf·文件