使用生成的输出文件名的 bash 争用条件



我正在尝试使用随机文件名生成输出。在此示例中,使用"cat"模拟"生成":

cat report.csv > "test_$(openssl rand -base64 102).csv"

我经常收到这样的错误:

-bash: test_Q6eheRaVfktCTCfWSU/tjRNA1y+6juwlyuo1lEId/7HZTCQIE7/rt+/9MlTI+pjT
9It3l7FtBldMmaqHNWpspwCI5kCpR+s51RA2o9xAZ6BrZ+7UBR5atK9qWdSO/N/X
BAnvDkGm.csv: No such file or directory

随机字符数越多,此错误的概率就越高,这表明存在争用条件。通过对随机字符使用变量来解决问题是显而易见的,这不是我要问的。相反,我的问题是:bash 执行的各个步骤是什么,竞争条件在哪里?

我本以为 bash 按如下方式执行命令:

  • 创建一个管道来捕获 openssl rand 的输出
  • fork/exec 打开 scl rand,将该文件句柄作为 stdout 传递,并等待进程完成(并检查错误状态(
  • 从管道读取以获取字符串插值中使用的值,然后关闭管道
  • 执行字符串内插以生成输出文件名
  • 打开输出文件
  • fork/exec cat,将输出文件的句柄作为标准输出传递
  • 等待该过程完成(并检查错误状态(,然后关闭输出文件

此处没有任何内容表明存在竞争条件。我可以想象 bash 而是并行运行 cat 并在进入输出文件之前打开另一个管道来缓冲其输出,但这也不会导致"没有这样的文件或目录"。

正如评论的那样,文件名中的斜杠是一个明显的问题,但即使没有斜杠也会发生错误。将随机字节数设置为 8 有时会产生这样的错误,没有斜杠和正确的字符数(因此没有隐藏斜杠(:

-bash: test_9od1IhDt5A4=.csv: No such file or directory

以下命令等待 2 秒钟,然后运行该命令。在出现奇怪错误消息的那些情况下,它会等待 4 秒。bash中是否有某种重复登录可以做到这一点?

cat report.csv > "test_$(sleep 2; openssl rand -base64 9).csv"

通过回显到 stderr 而不是休眠来确认双重执行:

cat report.csv > "test_$(echo foo 1>&2; openssl rand -base64 9).csv"

这里正在发生几件事。

关键部分是命令替换中的错误会导致它被计算两次。这似乎是苹果使用的bash版本中的错误。https://github.com/bminor/bash/blob/master/CHANGES 的更新日志说,对于版本bash-4.3-alpha:"修复了一个错误,当命令替换出现在失败的重定向中时,可能会导致对命令替换进行双重评估。我在"GNU bash,版本 3.2.57(1(-release (x86_64-apple-darwin18("上运行了我的测试,该版本预装在 macOS 上。

以简单方式重现此错误的步骤:

lap47:~/Documents> echo foo > "test_$(echo bar 1>&2; echo 'foo')"
bar
lap47:~/Documents> echo foo > "test_$(echo bar 1>&2; echo 'f/oo')"
bar
bar
-bash: test_f/oo: No such file or directory
lap47:~/Documents> 

第二个重要部分是,两次评估命令替换会调用两次 openssl rand,产生不同的随机数。第二次调用似乎用于在重定向失败后生成错误消息。这样,错误消息不会反映导致错误的条件。

最后,重定向失败的根本原因确实是文件名中的斜杠。此失败会导致再次调用替换,从而生成不同的文件名,该文件名可能不包含斜杠。

最新更新