与 rm <dir>/* 同时输出到 <dir> 有多安全

Mic*_*ael 5 bash wildcards rm parallelism

有时我需要删除目录的所有内容并在那里创建新文件。我可以做这样的事情并期望所有新文件保持完整:

% rm -rf regression/* & ( sleep 10 ; run_regression )
Run Code Online (Sandbox Code Playgroud)

在哪里run_regression为其输出文件添加时间戳,以便它们具有唯一的名称并将它们放入regression

我的想法是,shell 将解析regression/*预先存在的文件名的显式列表,然后rm将删除该显式列表中的文件,但不会删除run_regressionrm. 由于run_regression时间戳其文件应该没有名称冲突。

但是,我不太确定如何判断 shell 何时完成列出文件并rm开始工作。以上 10 秒是否足够?我可以做这样的事情bash

% rm -rf regression/* & ( wait_unil_names_are_resolved ; run_regression )
Run Code Online (Sandbox Code Playgroud)

每条评论澄清我确实在问外壳程序是否保证在调用该工具之前将通配符扩展为文件名,即使它是外壳程序非常熟悉的工具。我可以想象,shell 和工具的开发人员可能会想用工具管道通配符扩展;我希望虽然有标准可以防止这种情况。

ado*_*nis 5

尽管您的命令可能有效,但这是一个测试用例:

$ ls
$ echo * $(sleep 1)&touch file1
[1] 12798
$ file1

[1]+  Done                    echo * $(sleep 1)
Run Code Online (Sandbox Code Playgroud)

注意 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***
Run Code Online (Sandbox Code Playgroud)


Law*_*w29 4

这不安全。

您尚未指定您要解决的问题是什么。如果您的问题是您希望目录始终存在但不时清理,我建议明确删除早于检查文件的文件(睡眠 1 是我偏执):

touch regression.delete \
&& find regression \! -newer regression.delete -delete & \
&& sleep 1 \
&& run_regression
Run Code Online (Sandbox Code Playgroud)

如果您有子目录,则会出现问题,您可以改为编写

touch regression.delete \
&& find regression -mindepth 1 -maxdepth 1 \! -newer regression.delete -exec rm -rf '{}' \; & \
&& sleep 1 \
&& run_regression
Run Code Online (Sandbox Code Playgroud)

如果您的问题是您想尽快启动程序,如果目录可能暂时不存在并且它不是安装点,我通常会运行类似的命令

mkdir regression.new \
&& chmod --reference regression regression.new \
&& mv regression regression.delete \
&& mv regression.new regression \
&& rm -rf regression.delete & \
run_regression
Run Code Online (Sandbox Code Playgroud)

这应该允许您几乎立即启动 run_regression 。

回复您的编辑(并根据另一个答案的研究编辑我自己),必须在启动命令之前扩展通配符rm,但问题的关键是知道扩展是否在 shell 分叉后完成。据我所知,异步执行的 POSIX 规范没有明确指定一种或另一种方式,第 2.1 节当然暗示扩展是一种不同的操作,并且在命令的实际 fork/exec 之前,但测试(由@adonis,复制由我使用 bash 4.3.42(1)) 表明 bash 采用最有效的方式:如果通配符扩展需要时间,那么通过以下命令执行的修改可以很好地影响该扩展。因此,您最初的想法可能会删除您不想删除的文件。

我查看了 bash 源代码,execute_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. */
Run Code Online (Sandbox Code Playgroud)

  • 可以肯定的是,通配符扩展发生在 shell 分叉之后。如果通配符扩展由于文件系统非常慢而延迟,则另一个进程仍然应该运行。因此通配符扩展与启动另一个进程并行运行。*通常*通配符扩展比大型程序的初始化更快,但您不能依赖于此,并且在您测试时(因为目录列表位于缓存中)它可能比在实际情况下工作得更好。 (3认同)
  • 哦,我只是想到了这一点:当我遇到此类问题时,我通常在每次运行时将文件组织在一个(带时间戳的)子目录中。这样就可以很容易地保留(比如说)最近十次运行的结果。我通常会创建一个名为“last”的符号链接,该链接是在运行成功完成后由“ln -sf $timestamp last”创建的。这样“last”总是指向最后一次成功完成的运行。 (2认同)