## # 流程控制：for 循环

In this final chapter on flow control, we will look at another of the shell’s looping constructs. The for loop differs from the while and until loops in that it provides a means of processing sequences during a loop. This turns out to be very useful when programming. Accordingly, the for loop is a very popular construct in bash scripting.

A for loop is implemented, naturally enough, with the for command. In modern versions of bash, for is available in two forms.

### # for: 传统 shell 格式

The original for command’s syntax is:

for 命令语法是：

for variable [in words]; do
commands
done

Where variable is the name of a variable that will increment during the execution of the loop, words is an optional list of items that will be sequentially assigned to variable, and commands are the commands that are to be executed on each iteration of the loop.

The for command is useful on the command line. We can easily demonstrate how it works:

[me@linuxbox ~]$for i in A B C D; do echo$i; done
A
B
C
D

In this example, for is given a list of four words: “A”, “B”, “C”, and “D”. With a list of four words, the loop is executed four times. Each time the loop is executed, a word is as- signed to the variable i. Inside the loop, we have an echo command that displays the value of i to show the assignment. As with the while and until loops, the done keyword closes the loop.

The really powerful feature of for is the number of interesting ways we can create the list of words. For example, through brace expansion:

for 命令真正强大的功能是我们可以通过许多有趣的方式创建 words 列表。例如，通过花括号展开：

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

or pathname expansion:

[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

or command substitution:

#!/bin/bash
# longest-word : find longest string in a file
while [[ -n $1 ]]; do if [[ -r$1 ]]; then
max_word=
max_len=0
for i in $(strings$1); do
len=$(echo$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 In this example, we look for the longest string found within a file. When given one or more filenames on the command line, this program uses the strings program (which is included in the GNU binutils package) to generate a list of readable text “words” in each file. The for loop processes each word in turn and determines if the current word is the longest found so far. When the loop concludes, the longest word is displayed. 在这个示例中，我们要在一个文件中查找最长的字符串。当在命令行中给出一个或多个文件名的时候， 该程序会使用 strings 程序（其包含在 GNU binutils 包中），为每一个文件产生一个可读的文本格式的 “words” 列表。 然后这个 for 循环依次处理每个单词，判断当前这个单词是否为目前为止找到的最长的一个。当循环结束的时候，显示出最长的单词。 If the optional in words portion of the for command is omitted, for defaults to pro- cessing the positional parameters. We will modify our longest-word script to use this method: 如果省略掉 for 命令的可选项 words 部分，for 命令会默认处理位置参数。 我们将修改 longest-word 脚本，来使用这种方式： #!/bin/bash # longest-word2 : find longest string in a file for i; do if [[ -r$i ]]; then
max_word=
max_len=0
for j in $(strings$i); do
len=$(echo$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 As we can see, we have changed the outermost loop to use for in place of while. By omitting the list of words in the for command, the positional parameters are used instead. Inside the loop, previous instances of the variable i have been changed to the variable j. The use of shift has also been eliminated. 正如我们所看到的，我们已经更改了最外围的循环，用 for 循环来代替 while 循环。通过省略 for 命令的 words 列表， 用位置参数替而代之。在循环体内，之前的变量 i 已经改为变量 j。同时 shift 命令也被淘汰掉了。 Why i? 为什么是 i？ You may have noticed that the variable i was chosen for each of the for loop examples above. Why? No specific reason actually, besides tradition. The variable used with for can be any valid variable, but i is the most common, followed by j and k. 你可能已经注意到上面所列举的 for 循环的实例都选择 i 作为变量。为什么呢？ 实际上没有具体原因，除了传统习惯。 for 循环使用的变量可以是任意有效的变量，但是 i 是最常用的一个，其次是 j 和 k。 The basis of this tradition comes from the Fortran programming language. In For- tran, undeclared variables starting with the letters I, J, K, L, and M are automati- cally typed as integers, while variables beginning with any other letter are typed as real (numbers with decimal fractions). This behavior led programmers to use the variables I, J, and K for loop variables, since it was less work to use them when a temporary variable (as loop variables often are) was needed. It also led to the following Fortran-based witticism: “GOD is real, unless declared integer.” 这一传统的基础源于 Fortran 编程语言。在 Fortran 语言中，以字母 I、J、K、L 和 M 开头的未声明变量的类型 自动设为整形，而以其它字母开头的变量则为实数类型（带有小数的数字）。这种行为导致程序员使用变量 I、J和 K 作为循环变量， 因为当需要一个临时变量（正如循环变量）的时候，使用它们工作量比较少。这也引出了如下基于 Fortran 的俏皮话： “神是实数，除非是声明的整数。” ### # for: C 语言格式 Recent versions of bash have added a second form of for command syntax, one that resembles the form found in the C programming language. Many other languages support this form, as well: 最新版本的 bash 已经添加了第二种格式的 for 命令语法，该语法相似于 C 语言中的 for 语法格式。 其它许多编程语言也支持这种格式： for (( expression1; expression2; expression3 )); do commands done where expression1, expression2, and expression3 are arithmetic expressions and com- mands are the commands to be performed during each iteration of the loop. In terms of behavior, this form is equivalent to the following construct: 这里的 expression1、expression2和 expression3 都是算术表达式，commands 是每次循环迭代时要执行的命令。 在行为方面，这相当于以下构造形式： (( expression1 )) while (( expression2 )); do commands (( expression3 )) done expression1 is used to initialize conditions for the loop, expression2 is used to determine when the loop is finished, and expression3 is carried out at the end of each iteration of the loop. expression1 用来初始化循环条件，expression2 用来决定循环结束的时间，还有在每次循环迭代的末尾会执行 expression3。 Here is a typical application: 这里是一个典型应用： #!/bin/bash # simple_counter : demo of C style for command for (( i=0; i<5; i=i+1 )); do echo$i
done

When executed, it produces the following output:

[me@linuxbox ~]$simple_counter 0 1 2 3 4 In this example, expression1 initializes the variable i with the value of zero, expression2 allows the loop to continue as long as the value of i remains less than 5, and expression3 increments the value of i by one each time the loop repeats. 在这个示例中，expression1 初始化变量 i 的值为0，expression2 允许循环继续执行只要变量 i 的值小于5， 还有每次循环迭代时，expression3 会把变量 i 的值加1。 The C language form of for is useful anytime a numeric sequence is needed. We will see several applications for this in the next two chapters. C 语言格式的 for 循环对于需要一个数字序列的情况是很有用处的。我们将在接下来的两章中看到几个这样的应用实例。 ### # 总结 With our knowledge of the for command, we will now apply the final improvements to our sys_info_page script. Currently, the report_home_space function looks like this: 学习了 for 命令的知识，现在我们将对我们的 sys_info_page 脚本做最后的改进。 目前，这个 report_home_space 函数看起来像这样： 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
}

Next, we will rewrite it to provide more detail for each user’s home directory, and include the total number of files and subdirectories in each:

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
}

This rewrite applies much of what we have learned so far. We still test for the superuser, but instead of performing the complete set of actions as part of the if, we set some vari- ables used later in a for loop. We have added several local variables to the function and made use of printf to format some of the output.