人们想要学习 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 条评论