在POSIX目录循环中修改文件名是否始终安全?
测试场景
先创建一个包含20个文件的测试目录:
# mkdir test # touch test/{11..30} # ls test 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
以下是给每个文件名添加前缀9的重命名脚本:
#!/bin/sh dir='/tmp/test' for fullpath in "$dir"/* ; do filename=${fullpath##*/} mv "$fullpath" "${dir}/9${filename}" done
运行脚本后,目录内文件名都被修改:
# ./prog.sh # ls test 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
问题提出
这次测试里修改文件名是安全的,但如果目录内文件数量更多,或者包含递归子目录,先被重命名的文件(比如911、912这类)会不会被排到列表末尾,导致循环再次处理它们,进而陷入无限循环反复添加前缀9?
有人推测这取决于**globbing(通配符扩展)**的特性:比如它能处理的最大条目数,超过数目时是否会延迟扩展。那核心问题是:在POSIX规范下,这种循环修改文件名的操作是否始终安全?
解答
核心结论:符合POSIX标准的shell下,这种操作是绝对安全的,不会陷入无限循环
POSIX标准明确规定,shell的通配符扩展(比如"$dir"/*)是在循环体执行之前就完全完成的——也就是说,shell会先一次性把所有匹配"$dir"/*的文件路径展开成一个固定列表,后续循环只会依次处理这个列表里的每一项,不管循环过程中目录内的文件如何新增、修改、删除,都不会重新触发通配符扩展。
哪怕目录内文件数量极大,只要是遵循POSIX规范的shell,都会一次性完成通配符扩展并生成固定的待处理列表。所谓“延迟扩展”的情况不属于POSIX标准范畴,要么是部分非标准shell的特殊实现,要么是启用了特定扩展选项的场景,不属于常规POSIX环境的情况。
另外,递归子目录也不会影响这个结论——因为"$dir"/*只会匹配当前目录下的直接子项,不会递归进入子目录,所以子目录内的文件修改不会干扰当前循环的待处理列表。
简单来说:只要按照POSIX标准编写shell脚本,用for fullpath in "$dir"/*的方式遍历文件并修改文件名,就完全不用担心重复处理、无限循环的问题。
内容的提问来源于stack exchange,提问作者midnite

