使用 IFS 处理文件名中的空格

mso*_*ico 5 bash

问题如何重命名文件夹中名称以“_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
Run Code Online (Sandbox Code Playgroud)

不过,我想充分了解他的单行本。确切地说,我不明白的部分是:

while IFS= read -r -d '' file
Run Code Online (Sandbox Code Playgroud)

我知道 IFS 是“内部字段分隔符”,我想这是删除或忽略空格,但我不明白语法和选项。

我也想了解为什么--在 mv 之后是必要的。

有人可以帮忙吗?谢谢。

Gil*_*il' 5

read word1 word2 … rest 执行以下操作:

  1. 读一行。
  2. 当当前输入以反斜杠结尾时,读取另一行并将其附加到第一行,减去反斜杠换行符。
  3. 将输入分成单独的单词,其中IFS变量值中的任何字符都被视为单词分隔符。
  4. 将第一个词分配给变量word,将第二个词分配给变量word2,依此类推。
  5. 如果有剩余的单词,则将它们分配给变量rest,并保留内部的原始单词分隔符。

该选项会-r停用第 2 步,因此\行尾的 a 不被视为单词分隔符。

随着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 vs ${VAR} 并引用或不引用环境变量中单引号和双引号的意义是什么?也可能有兴趣)。

快速修复是添加双引号:

mv -- "$file" "$(echo "$file" | sed 's/_backup//g')"
Run Code Online (Sandbox Code Playgroud)

由于输入的产生方式,这恰好在这里起作用,但对于file它的一般值在少数情况下失败:

  • 如果 的值file由字符-后跟其中的一个或多个字母组成Eenecho则将其视为一个选项,不输出任何内容。
  • 命令替换会占用输出的最后一个换行符,因此如果$file以一个或多个换行符结尾,它们将被截断。

Bash 有一个字符串替换,可以在这里代替 sed 使用。它更健壮(您无需担心特殊字符的微妙之处)且速度更快。

mv -- "$file" "${file//_backup/}"
Run Code Online (Sandbox Code Playgroud)

尽管考虑到问题的要求,但这是错误的操作:它删除_backup了文件名中的任何位置,而不是仅在末尾。在这里做对了会更容易。

mv -- "$file" "${file%_backup}"
Run Code Online (Sandbox Code Playgroud)