人们想要学习 Unix shell 的一个常见原因是解锁批量处理的能力。 如果您想要对多个文件执行一系列操作,其中一种方法是构建一个迭代这些文件的命令。 在编程术语中,这称为执行控制,其中最常见的例子之一是 for 循环。
一个 for 循环是一个详细说明您希望计算机针对您指定的每个数据对象(例如文件)执行哪些操作的配方。
经典的 for 循环
一个容易尝试的循环是分析文件集合的循环。 这本身可能不是一个有用的循环,但它是一种安全的方式来向您证明您有能力单独处理目录中的每个文件。 首先,创建一个简单的测试环境,创建一个目录并将一些文件的副本放入其中。 最初任何文件都可以,但后面的示例需要图形文件(例如 JPEG、PNG 或类似文件)。 您可以使用文件管理器或在终端中创建文件夹并将文件复制到其中
$ mkdir example
$ cp ~/Pictures/vacation/*.{png,jpg} example
将目录更改为您的新文件夹,然后列出其中的文件,以确认您的测试环境符合您的预期
$ cd example
$ ls -1
cat.jpg
design_maori.png
otago.jpg
waterfall.png
循环遍历每个文件的语法如下:创建一个变量(例如 f 代表文件)。 然后定义您希望变量循环遍历的数据集。 在这种情况下,使用 * 通配符(* 通配符匹配所有内容)循环遍历当前目录中的所有文件。 然后用分号 (;) 终止这个介绍性子句。
$ for f in * ;
根据您的偏好,您可以选择在此处按 Return 键。 在语法完整之前,shell 不会尝试执行循环。
接下来,定义您希望循环的每次迭代发生什么。 为简单起见,使用 file 命令获取有关每个文件的一些数据,该文件由 f 变量表示(但在前面加上 $ 以告诉 shell 将变量的值替换为变量当前包含的任何内容)
do file $f ;
用另一个分号终止该子句并关闭循环
done
按 Return 键以启动 shell 循环遍历当前目录中的所有内容。 for 循环将每个文件一个接一个地分配给变量 f 并运行您的命令
$ for f in * ; do
> file $f ;
> done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
您也可以这样编写
$ for f in *; do file $f; done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
多行和单行格式对于您的 shell 来说是相同的,并且会产生完全相同的结果。
一个实际的例子
这是一个实际的例子,说明循环如何对日常计算有用。 假设您有一系列想要发送给朋友的度假照片。 您的照片文件很大,因此它们太大而无法通过电子邮件发送,并且不方便上传到您的 照片共享服务。 您想要创建更小的网络版本的照片,但您有 100 张照片,并且不想花时间一张一张地缩小每张照片。
首先,在 Linux、BSD 或 Mac 上使用您的软件包管理器安装 ImageMagick 命令。 例如,在 Fedora 和 RHEL 上
$ sudo dnf install ImageMagick
在 Ubuntu 或 Debian 上
$ sudo apt install ImageMagick
在 BSD 上,使用 ports 或 pkgsrc。 在 Mac 上,使用 Homebrew 或 MacPorts。
安装 ImageMagick 后,您可以使用一组新命令来操作照片。
为您即将创建的文件创建一个目标目录
$ mkdir tmp
要将每张照片缩小到原始大小的 33%,请尝试以下循环
$ for f in * ; do convert $f -scale 33% tmp/$f ; done
然后查看 tmp 文件夹以查看您缩放的照片。
您可以在循环中使用任意数量的命令,因此如果您需要对一批文件执行复杂的操作,您可以将整个工作流程放在 for 循环的 do 和 done 语句之间。 例如,假设您想将每张处理过的照片直接复制到您的 Web 主机上的共享照片目录,并从您的本地系统中删除照片文件
$ for f in * ; do
convert $f -scale 33% tmp/$f
scp -i seth_web tmp/$f seth@example.com:~/public_html
trash tmp/$f ;
done
对于 for 循环处理的每个文件,您的计算机都会自动运行三个命令。 这意味着如果您以这种方式仅处理 10 张照片,您就可以节省 30 个命令,并且可能至少可以节省同样多的时间。
限制您的循环
循环不一定总是查看每个文件。 您可能只想处理示例目录中的 JPEG 文件
$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done
$ ls -m tmp
cat.jpg, otago.jpg
或者,您可能需要重复执行某个操作特定次数,而不是处理文件。 for 循环的变量由您提供给它的任何数据定义,因此您可以创建一个迭代数字而不是文件的循环
$ for n in {0..4}; do echo $n ; done
0
1
2
3
4
更多循环
您现在已经知道足够的知识来创建您自己的循环。 在您熟悉循环之前,请在您要处理的文件的副本上使用它们,并尽可能多地使用带有内置安全措施的命令,以防止您破坏您的数据并犯下不可弥补的错误,例如意外地将整个文件目录重命名为相同的名称,每个文件都会覆盖另一个文件。
有关高级 for 循环主题,请继续阅读。
并非所有 shell 都是 Bash
for 关键字内置于 Bash shell 中。 许多类似的 shell 使用相同的关键字和语法,但有些 shell,例如 tcsh,使用不同的关键字,例如 foreach。
在 tcsh 中,语法在精神上相似,但比 Bash 更严格。 在以下代码示例中,不要在第 2 行和第 3 行中键入字符串 foreach?。 这是一个辅助提示,提醒您您仍在构建循环的过程中。
$ foreach f (*)
foreach? file $f
foreach? end
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
在 tcsh 中,foreach 和 end 都必须单独出现在单独的行上,因此您无法像在 Bash 和类似的 shell 中那样在一行上创建 for 循环。
使用 find 命令进行 For 循环
从理论上讲,您可以找到一个不提供 for 循环功能的 shell,或者您可能只是更喜欢使用具有附加功能的不同命令。
find 命令是实现 for 循环功能的另一种方式,因为它提供了多种方式来定义循环中包含哪些文件的范围,以及 并行处理的选项。
find 命令旨在帮助您在硬盘驱动器上查找文件。 它的语法很简单:您提供要搜索的位置的路径,并且 find 查找所有文件和目录
$ find .
.
./cat.jpg
./design_maori.png
./otago.jpg
./waterfall.png
您可以通过添加名称的某些部分来过滤搜索结果
$ find . -name "*jpg"
./cat.jpg
./otago.jpg
find 的优点在于,它找到的每个文件都可以使用 -exec 标志馈送到循环中。 例如,要仅缩小示例目录中的 PNG 照片
$ find . -name "*png" -exec convert {} -scale 33% tmp/{} \;
$ ls -m tmp
design_maori.png, waterfall.png
在 -exec 子句中,方括号字符 {} 代表 find 正在处理的任何项目(换句话说,一次一个,任何以 PNG 结尾的已定位的文件)。 -exec 子句必须以分号终止,但 Bash 通常会尝试将分号用于自身。 您可以使用反斜杠 (\;) “转义”分号,以便 find 知道将该分号视为其终止字符。
find 命令非常擅长它所做的事情,有时它可能太好了。 例如,如果您重复使用它来查找用于另一个照片处理的 PNG 文件,您会收到一些错误
$ find . -name "*png" -exec convert {} -flip -flop tmp/{} \;
convert: unable to open image `tmp/./tmp/design_maori.png':
No such file or directory @ error/blob.c/OpenBlob/2643.
...
似乎 find 已经定位了所有 PNG 文件——不仅是您当前目录 (.) 中的文件,还有您之前处理并放置在 tmp 子目录中的文件。 在某些情况下,您可能希望 find 搜索当前目录以及其中的所有其他目录(以及这些目录中的所有目录)。 它可以是一个强大的递归处理工具,尤其是在复杂的文件结构中(例如包含专辑目录和其中充满音乐文件的音乐艺术家目录),但您可以使用 -maxdepth 选项来限制这一点。
要仅查找当前目录中的 PNG 文件(不包括子目录)
$ find . -maxdepth 1 -name "*png"
要在当前目录加上一个额外的子目录级别中查找和处理文件,请将最大深度增加 1
$ find . -maxdepth 2 -name "*png"
它的默认设置是下降到所有子目录中。
循环以获得乐趣和收益
您使用循环越多,节省的时间和精力就越多,并且您可以处理的任务也就越大。 您只是一个用户,但通过精心设计的循环,您可以让您的计算机完成繁重的工作。
你可以也应该像对待其他命令一样对待循环,随时掌握它,以便在你需要在多个文件上重复一两个操作时使用。然而,它也是通往严肃编程的合理途径,因此,如果你必须在任意数量的文件上完成一项复杂的任务,请花一些时间来规划你的工作流程。如果你可以在一个文件上实现你的目标,那么将这个可重复的过程封装在一个 for 循环中就相对简单了,唯一需要的“编程”就是理解变量的工作方式,以及有足够的组织能力将未处理的文件与已处理的文件分开。稍微练习一下,你就可以从一个 Linux 用户变成一个知道如何编写循环的 Linux 用户,所以出去让你的电脑为你工作吧!
10 条评论