
Bash 循环,命令行高级用户的利器
了解如何在 Bash 编程多部分系列的第三篇文章中使用循环执行迭代操作。
作者注:本系列文章最初出现在 Opensource.com 上。 原始文章由两个系列组成,其余的作为独立文章发表。这些文章已合并为一个 单一 系列,并为此目的进行了更新和修改。
简介
Bash 是一种强大的编程语言,非常适合在命令行和 shell 脚本中使用。本系列基于我的 三卷 Linux 自学课程,探讨了在命令行界面 (CLI) 上将 Bash 用作编程语言。
本系列的 第一篇文章 探讨了一些使用 Bash 进行的简单命令行编程,包括使用变量和控制运算符。第二篇文章 研究了文件类型、字符串类型、数值类型和各种逻辑运算符,这些运算符提供了 Bash 中的执行流控制逻辑和不同类型的 shell 扩展。第三篇文章将探讨循环的使用,以执行各种类型的迭代操作以及控制这些循环的方法。
循环
我用过的每种编程语言都至少有两种循环结构,它们提供了执行重复操作的各种能力。我经常使用 for 循环,但也发现 while 和 until 循环很有用。
for 循环
在我看来,Bash 对 for 命令的实现比大多数语言更灵活,因为它能够处理非数值型的值;相比之下,例如,标准的 C 语言 for 循环只能处理数值型的值。
Bash 版本 for 命令的基本结构很简单
for Var in list1 ; do list2 ; done
这可以翻译为:“对于 list1 中的每个值,将 $Var 设置为该值,然后使用该值执行 list2 中的程序语句;当 list1 中的所有值都已使用完毕后,循环结束,退出循环。” list1 中的值可以是简单的显式值字符串,也可以是本系列第二篇文章中描述的命令替换的结果。我经常使用这种结构。
要尝试一下,请确保 ~/testdir 是当前工作目录 (PWD)。如果还没有此名称的目录,请创建它。清理目录,然后查看一个简单的 for 循环示例,该循环以显式值列表开始。此列表是字母数字值的混合 - 但不要忘记所有变量都是字符串,并且可以这样处理。
$ rm *
$ for I in a b c d 1 2 3 4 ; do echo $I ; done
a
b
c
d
1
2
3
4
这是一个更有用的版本,变量名称更有意义
$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Department $Dept" ; done
Department Human Resources
Department Sales
Department Finance
Department Information Technology
Department Engineering
Department Administration
Department Research
创建一些目录(并在执行此操作时显示一些进度信息)
$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human Resources
Working on Department Sales
Working on Department Finance
Working on Department Information Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Human Resources'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Information Technology'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Sales
$Dept 变量必须用引号括起来放在 mkdir 语句中;否则,由两部分组成的部门名称(例如“Information Technology”)将被视为两个独立的部门。这突出了我喜欢遵循的最佳实践:所有文件和目录名称都应为单个单词。尽管大多数现代操作系统都可以处理名称中的空格,但系统管理员需要付出额外的努力来确保在脚本和 CLI 程序中考虑到这些特殊情况。(即使它们很烦人,也几乎肯定应该考虑它们,因为你永远不知道你会有什么文件。)
因此,再次删除 ~/testdir 中的所有内容 - 再做一次
$ rm -rf * ; ll
total 0
$ for Dept in Human-Resources Sales Finance Information-Technology Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human-Resources
Working on Department Sales
Working on Department Finance
Working on Department Information-Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Human-Resources
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Information-Technology
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Sales
假设有人要求列出特定 Linux 计算机上的所有 RPM 以及每个 RPM 的简短描述。当我为北卡罗来纳州工作时,就发生过这种情况。由于当时州政府机构“未批准”使用开源软件,而我在我的台式计算机上使用了 Linux,因此那些尖头顶老板 (PHB) 需要一份安装在我计算机上的每件软件的列表,以便他们可以“批准”例外情况。
你将如何处理这个问题?以下是一种方法,从 rpm –qa 命令提供 RPM 的完整描述的知识开始,包括 PHB 想要的两个项目:软件名称和简要摘要。
我逐步构建了最终结果。
首先,列出所有 RPM。
$ rpm -qa
perl-HTTP-Message-6.18-3.fc29.noarch
perl-IO-1.39-427.fc29.x86_64
perl-Math-Complex-1.59-429.fc29.noarch
lua-5.3.5-2.fc29.x86_64
java-11-openjdk-headless-11.0.ea.28-2.fc29.x86_64
util-linux-2.32.1-1.fc29.x86_64
libreport-fedora-2.9.7-1.fc29.x86_64
rpcbind-1.2.5-0.fc29.x86_64
libsss_sudo-2.0.0-5.fc29.x86_64
libfontenc-1.1.3-9.fc29.x86_64
<snip>
添加 sort 和 uniq 命令以对列表进行排序并打印唯一的 RPM,因为可能安装了名称相同的某些 RPM。
$ rpm -qa | sort | uniq
a2ps-4.14-39.fc29.x86_64
aajohan-comfortaa-fonts-3.001-3.fc29.noarch
abattis-cantarell-fonts-0.111-1.fc29.noarch
abiword-3.0.2-13.fc29.x86_64
abrt-2.11.0-1.fc29.x86_64
abrt-addon-ccpp-2.11.0-1.fc29.x86_64
abrt-addon-coredump-helper-2.11.0-1.fc29.x86_64
abrt-addon-kerneloops-2.11.0-1.fc29.x86_64
abrt-addon-pstoreoops-2.11.0-1.fc29.x86_64
abrt-addon-vmcore-2.11.0-1.fc29.x86_64
<snip>
由于这提供了您要查看的 RPM 的正确列表,因此您可以将其用作循环的输入列表,该循环将打印每个 RPM 的所有详细信息。
$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done
此代码生成的数据量远远超出你的需求。请注意,循环已完成。下一步是仅提取 PHB 请求的信息。因此,添加一个 egrep 命令,该命令用于选择 ^Name 或 ^Summary。脱字符 (^) 指定行的开头;因此,将显示以 Name 或 Summary 开头的任何行。
$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary"
Name : a2ps
Summary : Converts text and other types of files to PostScript
Name : aajohan-comfortaa-fonts
Summary : Modern style true type font
Name : abattis-cantarell-fonts
Summary : Humanist sans serif font
Name : abiword
Summary : Word processing program
Name : abrt
Summary : Automatic bug detection and reporting tool
<snip>
你可以在上面的命令中尝试使用 grep 代替 egrep,但它将不起作用。你也可以通过 less 过滤器管道输出此命令来探索结果。最终命令序列如下所示
$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary" > RPM-summary.txt
此命令行程序在一行中使用了管道、重定向和 for 循环。它将我们的小型 CLI 程序的输出重定向到一个文件,该文件可以在电子邮件中使用或用作其他用途的输入。
这种逐步构建程序的过程使你能够查看每个步骤的结果,并确保它按预期工作并提供所需的结果。
从这项练习中,PHB 收到了超过 1,900 个单独 RPM 包的列表。我非常怀疑有人读过这份列表。但我给了他们他们所要求的,并且我再也没有听到他们提起这件事。
其他循环
Bash 中还有两种类型的循环结构:while 和 until 结构,它们在语法和功能上非常相似。这些循环结构的基本语法很简单
while [ expression ] ; do list ; done
和
until [ expression ] ; do list ; done
第一个的逻辑是:“只要表达式评估为真,就执行程序语句列表。当表达式评估为假时,退出循环。”第二个的逻辑是:“直到表达式评估为真,才执行程序语句列表。当表达式评估为真时,退出循环。”
While 循环
while 循环用于在逻辑表达式评估为真时(只要)执行一系列程序语句。你的 PWD 应该仍然是 ~/testdir。
while 循环最简单的形式是永远运行的循环。以下形式使用 true 语句始终生成“true”返回代码。你也可以使用简单的“1” - 这将同样有效 - 但这说明了 true 语句的用法
$ X=0 ; while [ true ] ; do echo $X ; X=$((X+1)) ; done | head
0
1
2
3
4
5
6
7
8
9
现在,你已经研究了该 CLI 程序的各个部分,所以它应该更有意义了。首先,它将 $X 设置为零,以防它具有先前程序或 CLI 命令遗留下来的值。然后,由于逻辑表达式 [ true ] 始终评估为 1(真),因此 do 和 done 之间的程序指令列表将永远执行 - 或者直到你按下 Ctrl+C 或以其他方式向程序发送信号 2。这些指令是一个算术扩展,用于打印 $X 的当前值,然后将其递增 1。
《系统管理员的 Linux 哲学》的宗旨之一是力求优雅,实现优雅的一种方法是简洁。你可以通过使用变量递增运算符 ++ 来简化此程序。在第一个实例中,打印变量的当前值,然后递增变量。这通过将 ++ 运算符放在变量之后来指示
$ X=0 ; while [ true ] ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
现在从程序的末尾删除 | head 并再次运行它。
在此版本中,变量在其值打印之前递增。这通过将 ++ 运算符放在变量之前来指定。你能看出区别吗?
$ X=0 ; while [ true ] ; do echo $((++X)) ; done | head
1
2
3
4
5
6
7
8
9
你已将两个语句简化为一个语句,该语句打印变量的值并递增该值。还有一个递减运算符 —。
你需要一种在特定数字处停止循环的方法。为此,请将 true 表达式更改为实际的数值评估表达式。让程序循环到 5 并停止。在下面的示例代码中,你可以看到 -le 是“小于或等于”的逻辑数值运算符。这意味着:“只要 $X 小于或等于 5,循环将继续。当 $X 递增到 6 时,循环终止。”
$ X=0 ; while [ $X -le 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
5
Until 循环
until 命令与 while 命令非常相似。不同之处在于,它将继续循环,直到逻辑表达式评估为“true”。看看这种结构的最简单形式
$ X=0 ; until false ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
它使用逻辑比较来计数到特定值
$ X=0 ; until [ $X -eq 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
[student@test1 ~]$ X=0 ; until [ $X -eq 5 ] ; do echo $((++X)) ; done
1
2
3
4
5
总结
到目前为止,本系列探讨了一些用于构建 Bash 命令行程序和 shell 脚本的强大工具。在本文中,我们研究了执行比较操作和创建迭代循环以执行重复性任务。
但是,我们仅仅触及了你可以使用 Bash 完成的许多有趣事情的表面。
我发现学习 Bash 编程的最佳方法是动手实践。找到一个需要多个 Bash 命令的简单项目,并从中创建一个 CLI 程序。系统管理员执行的许多任务都适合 CLI 编程,我确信你很容易找到需要自动化的任务。接下来:探索使用 Bash 进行的简单自动化。
1 条评论
评论已关闭。