有时我需要删除目录的所有内容并在那里创建新文件。我可以做这样的事情并期望所有新文件保持不变:
% rm -rf regression/* & ( sleep 10 ; run_regression )
在哪里run_regression
为其输出文件添加时间戳,以便它们具有唯一的名称并将它们放置在regression
?
我的想法是 shell 会解析regression/*
为一个明确的列表预先存在的文件名,然后rm
将删除该显式列表上的文件,但不会删除run_regression
与 .filename 同时创建的新文件rm
。由于run_regression
其文件带有时间戳,因此不应出现名称冲突。
但是,我不太确定如何判断 shell 何时完成列出文件并rm
开始工作。以上10秒够吗?我可以做这样的事情吗bash
:
% rm -rf regression/* & ( wait_unil_names_are_resolved ; run_regression )
根据评论澄清,我确实在询问 shell 是否保证在调用该工具之前将通配符扩展为文件名,即使它是 shell 熟悉的工具。我可以想象 shell 和工具的开发人员可能会忍不住使用该工具进行管道通配符扩展;我希望有标准可以防止这种情况发生。
答案1
尽管您的命令可能有效,但这里有一个测试用例:
$ ls
$ echo * $(sleep 1)&touch file1
[1] 12798
$ file1
[1]+ Done echo * $(sleep 1)
请注意,file1 并未输入,它是 echo 命令的输出。
编辑:
另一个测试运行:
$ ls
$ touch file1
$ for i in {1..5000}; do rm * & touch file$i; wait;done|grep file
rm: cannot remove '*': No such file or directory
***previous line repeated 14 times***
答案2
这不安全。
您尚未指定您要解决的问题是什么。如果您的问题是您希望目录始终存在但不时清理,我建议明确删除早于检查文件的文件(睡眠 1 是我偏执):
touch regression.delete \
&& find regression \! -newer regression.delete -delete & \
&& sleep 1 \
&& run_regression
如果您有子目录,则会出现问题,您可以改为编写
touch regression.delete \
&& find regression -mindepth 1 -maxdepth 1 \! -newer regression.delete -exec rm -rf '{}' \; & \
&& sleep 1 \
&& run_regression
如果您的问题是您想尽快启动程序,如果目录可能暂时不存在并且它不是安装点,我通常会运行类似的命令
mkdir regression.new \
&& chmod --reference regression regression.new \
&& mv regression regression.delete \
&& mv regression.new regression \
&& rm -rf regression.delete & \
run_regression
这应该允许您几乎立即启动 run_regression 。
回复您的编辑(并根据另一个答案的研究编辑我自己),必须在rm
启动命令之前扩展通配符,但问题的关键是知道扩展是否在 shell 分叉后完成。异步执行的 POSIX 规范据我所知,没有明确指定一种或另一种方式,第 2.1 节当然暗示扩展是一种不同的操作,并且在命令的实际 fork/exec 之前,但测试(由@adonis,由我使用 bash 4.3 复制) .42(1)) 表明 bash 采用最有效的方式:如果通配符扩展需要时间,那么通过以下命令执行的修改可以很好地影响该扩展。因此,您最初的想法可能会删除您不想删除的文件。
我查看了 bash 源代码,然后执行_cmd.c明确指出分叉是在单词扩展之前完成的:
3922 | /* If we're in a pipeline or run in the background, set DOFORK so we
3923 | make the child early, before word expansion. This keeps assignment
3924 | statements from affecting the parent shell's environment when they
3925 | should not. */
答案3
rm -rf regression/*
运行在平行下和( sleep 10 ; run_regression )
。这意味着您无法保证事物的顺序。rm -rf regression/*
首先收集目录中的文件列表regression
,然后调用rm
删除它们。这不是凭空发生的,它是 shell 在评估 command 的过程中完成的工作rm -rf regression/*
,并且是在操作符引起的 fork 之后发生的&
。如果收集步骤花费的时间少于 10 秒,则创建的文件run_regression
是安全的。如果收集步骤花费超过 10 秒才能到达由 所创建的文件run_regression
,则该文件将被删除。
删除文件实际上不会影响run_regression
,除非关闭文件并重新打开它。删除文件不会影响打开该文件的进程:该文件保持存在,没有目录条目(即硬链接计数为0),直到所有打开该文件的进程都将其关闭。但您将无法访问该程序的输出,因为它将被删除。
所以不要这样做。不要依赖时间:如果有 10 秒这样高的延迟,它会在测试期间工作(特别是因为在测试期间可能文件很少、缓存很热、没有 I/O 峰值、没有系统暂停等)你的测试),但迟早它会在生产中失败。
如果确实想保留该目录并删除其中的文件,请先进行文件名收集。
files_to_delete=(regression/*)
rm -rf "${files_to_delete[@]}" & run_regression
(这假设一个带有数组的 shell。在普通的 sh 中,使用set regression/*; rm -rf "$@" & run_regression
。)当然,这假设这些文件run_regression
只创建不存在的文件,如果它覆盖现有文件,那么这些文件将被删除。
您可能不需要所有这些复杂性:只需运行
rm -rf regression/*
run_regression
除非文件列表太大以至于无法放入缓存,或者除非文件系统的写入操作异常缓慢,否则收集名称列表比删除它们要长,因此不会对性能产生影响。
如果删除操作的性能确实很差(这又是不寻常的),请创建一个新目录。
mv regression regression.old
mkdir regression
rm -rf regression.old &
run_regression
答案4
只有使用新文件名才安全。 shell 知道文件名,而不是它们的 inode 等,并在运行命令之前进行通配符(通配符扩展)。根据POSIX:
2.6.6 路径名扩展
字段分割后,如果
set -f
未生效,则应使用中描述的算法扩展生成的命令行中的每个字段模式匹配表示法,符合规则用于文件名扩展的模式。
也就是说,这是在实际执行命令之前发生的解析中明确定义的步骤。 POSIX 中的大多数复杂情况都处理重定向和作业。本示例中没有任何内容,因此适用的是:
- 非变量赋值或重定向的词应扩展。如果扩展后仍有任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。
问题中显示的示例看起来没有删除任何目录。如果您碰巧依赖于可能已被删除的子目录的存在,则同样的警告适用。
大概是你的时间戳(十秒是不同的是秒在时间戳中)将是结果文件名的一部分。