正在讨论中如何重命名文件夹中名称以“_backup”结尾的所有文件@Radu Rădeanu 给出了一个很好的答案,对我也很有用:
find . -type f -name '*.jpg_backup' -print0 \
| while IFS= read -r -d '' file ; do mv -- "$file" \
"$(echo $file | sed 's/_backup//g')"; done
但是,我想完全理解他的这句话。确切地说,我不明白的部分是:
while IFS= read -r -d '' file
我知道 IFS 是“内部字段分隔符”,我认为这是删除或忽略空格,但我不明白语法和选项。
我也想了解为什么--
mv 之后需要有。
有人能帮忙吗?谢谢。
答案1
read word1 word2 … rest
执行以下操作:
- 读一行。
- 当当前输入以反斜杠结尾时,读取另一行并将其附加到第一行,减去反斜杠换行符。
- 将输入分成单独的单词,其中变量值中的任何字符
IFS
都被视为单词分隔符。 - 将第一个字分配给变量
word
,将第二个字分配给变量word2
,等等。 - 如果还有剩余的单词,则将它们分配给变量
rest
,并保留里面原来的单词分隔符。
该选项-r
停用步骤 2,因此\
行尾的 不被视为单词分隔符。
设置IFS
为空字符串后,没有单词分隔符,因此整行都是一个大单词:步骤 3 不执行任何操作,步骤 5 最终将原始行分配给指定的变量。关于为什么IFS
在此位置设置,请参阅为什么while IFS= read
经常使用 ,而不是IFS=; while read..
?。
该选项-d
改变了行的概念:通常一行以换行符结尾;使用-d ''
(空参数-d
),bash 将读取由空字节分隔的输入记录。
结果是,find … -print0 | while IFS= read -r -d '' file; do …
对于打印的每个匹配都执行循环体find
,无论文件名中可能出现什么特殊字符。
至于--
in mv -- "$file"
,如果 的值file
以破折号开头,则它会出现:mv
会将其解释为选项。在此特定情况下,它不是必需的,因为此find
命令的输出始终以 开头./
。--
在脚本中系统地使用可以说是良好的卫生习惯。
此代码片段存在缺陷:\[?*
由于位的原因,它仍然无法处理包含空格或 的文件名"$(echo $file | sed 's/_backup//g')"
。变量扩展(如 )$file
不仅仅替换变量的值:它使用 的值将变量拆分为单词IFS
(就像 一样read
),并将每个单词视为通配符模式,如果匹配任何文件,则将其替换为匹配文件列表。要避免此行为,请写入"$file"
。这是一条通用的 shell 编程规则:始终在变量替换(以及命令替换)周围加上双引号$(…)
,除非您知道为什么需要省略它们。(如果您想要了解细节,请参阅什么时候需要双引号?;$VAR 与 ${VAR} 以及引用还是不引用和环境变量中的单引号和双引号有什么意义?也可能会引起人们的兴趣)。
快速解决方法是添加双引号:
mv -- "$file" "$(echo "$file" | sed 's/_backup//g')"
由于输入的产生方式,这种情况在这里恰好有效,但对于一般值,它file
在少数情况下会失败:
- 如果 的值
file
由 字符-
和 后面一个或多个字母组成Een
,echo
则将其视为一个选项并且不输出任何内容。 - 命令替换会占用输出的最后换行符,因此如果
$file
以一个或多个换行符结尾,它们将被截断。
Bash 有一个字符串替换功能,可以在这里代替 sed 使用。它更强大(您无需担心特殊字符的细微差别)并且速度更快。
mv -- "$file" "${file//_backup/}"
尽管考虑到问题要求,这是错误的操作:它删除了_backup
文件名中的任何位置,而不是只删除末尾。在这里做正确的事会更容易。
mv -- "$file" "${file%_backup}"
答案2
如果您的文件名恰好以连字符开头,则需要--
将 mv 选项与实际文件名参数分开。
如果您的文件名以空格开头或结尾,则 read with 的咒语IFS=
将确保file
变量包含您想要的确切文件名。
该-d ''
选项读取零字节分隔的“行”而不是换行符分隔的行 - 这是因为您使用了 find 的-print0
选项。