考虑一下我们有很多照片的名称如DSC_20170506_170809.JPEG
。为了重命名照片以使它们遵循模式Paris_20170506_170809.JPEG
,我编写了以下完美运行的脚本。
for file in *.JPEG; do mv ${file} ${file/DSC/Paris}; done
我的问题是,我们如何使用循环while
而不是for
循环来编写这个脚本?
答案1
这里使用循环没有任何问题while
。你只需要做对:
set -- *.jpeg
while (($#)); do
mv -- "${1}" "${1/DSC/Paris}"
shift
done
上面的循环while
与循环一样可靠for
(它将适用于任何文件名),虽然后者在许多情况下是最合适使用的工具,但前者是一个有效的替代方案1,有其用途(例如上面可以一次处理三个文件或仅处理一定数量的参数等)。
所有这些命令(set
、while..do..done
和shift
)都记录在 shell 手册中,并且它们的名称是不言自明的......
set -- *.jpeg
# set the positional arguments, i.e. whatever that *.jpeg glob expands to
while (($#)); do
# execute the 'do...' as long as the 'while condition' returns a zero exit status
# the condition here being (($#)) which is arithmetic evaluation - the return
# status is 0 if the arithmetic value of the expression is non-zero; since $#
# holds the number of positional parameters then 'while (($#)); do' means run the
# commands as long as there are positional parameters (i.e. file names)
mv -- "${1}" "${1/DSC/Paris}"
# this renames the current file in the list
shift
# this actually takes a parameter - if it's missing it defaults to '1' so it's
# the same as writing 'shift 1' - it effectively removes $1 (the first positional
# argument) from the list so $2 becomes $1, $3 becomes $2 and so on...
done
1:它不是文本处理工具的替代品,所以绝不使用while
循环来处理文本。
答案2
for
-循环通常会重新完成静止的数据。也就是说,数据在循环过程中或在已知的时间间隔内不会改变。
while
当不知道需要多少次迭代时,通常使用循环,例如提示用户输入并验证响应直到正确,或者循环从文件或管道读取的数据。
在这种情况下,您将循环直接从当前目录读取的文件名,由 shell 扩展文件通配模式赋予循环。循环for
是正确使用的循环结构。循环while
只会使事情变得复杂,并使代码更难以阅读和正确执行。
这是一个while
与循环执行相同操作的循环示例for
:
printf '%s\n' DSC*.JPEG |
while read -r name; do
[ ! -f "Paris${name#DSC}" ] && mv "$name" "Paris${name#DSC}"
done
这里有问题:
- 文件名不能包含换行符,因为循环读取换行符分隔的名称。我们可以通过获取
printf
输出\0
分隔的文件名并read
读取它们来解决这个问题,但这会使它变得更加复杂(并且不可移植)。 - 没有任何收获。名称列表仍然是静态的,我们引入了一个冗余步骤来将这些名称传递到
printf
循环,该步骤带来了我们可以允许文件名包含哪些字符的问题(请参阅上一点)。
实际上,您的代码唯一的问题是变量扩展未加引号。
答案3
until
如果您愿意,还可以使用 Bash循环。
set -- *.jpeg
until ((! $#))
do
mv -- "${1}" "${1/DSC/Paris}"
shift
done