问题: 我正在寻找一种方法来重命名或复制文件而不覆盖目标文件(如果存在),然后检查移动或复制操作是否成功。我正在寻找一种方法,可以与 MacOS/Unix 上安装的 BSD 版本的 mv/cp 以及 Linux 上的 GNU coreutils 版本一起使用。
解决方案尝试:
在所有版本的 mv/cp 中,我可以防止使用以下标志覆盖目标文件-n
:
mv -n file1 file2
cp -n file1 file2
类似的问题建议使用退出状态来测试 mv 和 cp 是否成功,如果成功则为 0,如果发生错误则为 >0。但是,对于 mv/cp 的两个版本,当目标文件已存在并且-n
使用了标志时,退出代码为 0 。
我能想到的唯一其他选择是也使用该-v
标志,并查看命令的输出:
mv -nv file1 file2
cp -nv file1 file2
-nv
但是,当使用标志且 file2 已存在时,GNU 和 BSD 版本的 mv/cp 的行为有所不同:GNU 版本的 mv/cp 不返回任何内容,而 BSD 版本则返回file2 not overwritten
。
我们之前的方法是先检查目标文件是否存在,然后进行mv/cp操作。不管你相信与否,这会导致问题,因为目标文件有时会在执行检查和执行 mv/cp 操作之间由另一个进程创建。
有没有一种方法可以使用 BSD 和 GNU 版本的 mv/cp 来完成这项任务?
或者,有没有办法使用 Python 2 来做到这一点?我找不到使用 os.rename() 执行此操作的方法
答案1
如果你有bash
-https://stackoverflow.com/questions/13828544/atomic-create-file-if-not-exists-from-bash-script
set -o noclobber { > file ; } &> /dev/null
如果不存在名为 file 的文件,则此命令将创建一个名为 file 的文件。如果存在名为 file 的文件,则不执行任何操作(但返回非零返回代码)。
即首先使用此技术创建一个空文件。如果成功,您就可以覆盖空文件。
对于 python 来说也是如此。用于os.open()
创建一个空文件,确保包含O_EXCL
在标志中。 (“有关标志和模式值的说明,请参阅 C 运行时文档。”请参阅POSIX标准/Linux 手册页)。
bash 技术是O_EXCL
在幕后使用的。还有RENAME_NOREPLACE
,但它是 Linux 中相对较新的添加,我认为 OS X 上不存在它。
答案2
答案3
cp -n
如果要求覆盖文件,FreeBSD 确实会返回失败:
$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ cp -n foo.1 foo.2 || echo fail
fail
$ rm foo.2
$ # this should succeed
$ cp -n foo.1 foo.2 || echo fail
$ exit
您说得对,mv
即使目标存在,FreeBSD 也会返回成功:
$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ mv -n foo.1 foo.2 || echo fail
$ exit
一种解决方法是使用的结果代码&&
,如下所示:mv
[ ! -f src-file ]
$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
fail
$ rm foo.2
$ # this should succeed
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
$ exit
在 GNU 下,这两个实用程序都不会按照您想要的方式执行。同样的解决方法mv
在 Ubuntu 上也适用,因此 GNU 成为cp
剩下的一个问题案例。
顺便说一句,我听到您对在调用之前测试目标文件的竞争条件的评论cp
,但令我震惊的是,即使cp
做了正确的事情,也会出现相同的竞争条件。机会之窗可能会更小,但我的直觉是它仍然存在。然而,IAAE。
由于解决方法mv
适用于两个平台,也许这个解决方法就足够了:
$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
fail
$ rm -f TEMPFILE
$ rm foo.2
$ # this should succeed
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
$ rm -f TEMPFILE
$ exit