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

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

正在讨论中如何重命名文件夹中名称以“_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执行以下操作:

  1. 读一行。
  2. 当当前输入以反斜杠结尾时,读取另一行并将其附加到第一行,减去反斜杠换行符。
  3. 将输入分成单独的单词,其中变量值中的任何字符IFS都被视为单词分隔符。
  4. 将第一个字分配给变量word,将第二个字分配给变量word2,等等。
  5. 如果还有剩余的单词,则将它们分配给变量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由 字符-和 后面一个或多个字母组成Eenecho则将其视为一个选项并且不输出任何内容。
  • 命令替换会占用输出的最后换行符,因此如果$file以一个或多个换行符结尾,它们将被截断。

Bash 有一个字符串替换功能,可以在这里代替 sed 使用。它更强大(您无需担心特殊字符的细微差别)并且速度更快。

mv -- "$file" "${file//_backup/}"

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

mv -- "$file" "${file%_backup}"

答案2

如果您的文件名恰好以连字符开头,则需要--将 mv 选项与实际文件名参数分开。

如果您的文件名以空格开头或结尾,则 read with 的咒语IFS=将确保file变量包含您想要的确切文件名。

-d ''选项读取零字节分隔的“行”而不是换行符分隔的行 - 这是因为您使用了 find 的-print0选项。

相关内容