人们想要学习 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 语句之间。 例如,假设你想将每张处理过的照片直接复制到你的网络主机上的共享照片目录,并从你的本地系统删除照片文件
$ 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 条评论