人们想要学习 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 代表 file)。 然后定义您希望变量循环遍历的数据集。 在这种情况下,使用 * 通配符循环遍历当前目录中的所有文件(* 通配符匹配所有内容)。 然后用分号 (;) 结束这个介绍性子句。
$ 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 语句之间。 例如,假设您想将每张处理过的照片直接复制到您的网络主机上的共享照片目录,并从您的本地系统中删除照片文件
$ 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 条评论