移动或复制而不覆盖并检查是否成功

移动或复制而不覆盖并检查是否成功

问题: 我正在寻找一种方法来重命名或复制文件而不覆盖目标文件(如果存在),然后检查移动或复制操作是否成功。我正在寻找一种方法,可以与 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

如果文件位于同一文件系统上,那么您可以创建一个硬链接

ln SRC DEST

如果成功,您就可以删除源文件。

rm SRC

答案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

相关内容