PHP中如何复制文件而不覆盖现有文件?

8
当您使用PHP的copy函数时,该操作会盲目地复制目标文件,即使它已经存在。如果要安全地复制文件,只有在没有现有文件的情况下才执行复制操作,应该如何处理?
4个回答

8
明显的解决方案是调用file_exists来检查文件是否存在,但这样做可能会导致竞态条件。在调用file_existscopy之间,另一个文件有可能被创建。检查文件是否存在的唯一安全方法是使用fopen

当您调用fopen时,请将模式设置为“x”。这告诉fopen创建文件,但仅在文件不存在时。如果它存在,fopen将失败,您将知道无法创建该文件。如果成功,则会在目标位置创建一个文件,您可以安全地复制该文件。以下是示例代码:

// The PHP copy function blindly copies over existing files.  We don't wish
// this to happen, so we have to perform the copy a bit differently.  The
// only safe way to ensure we don't overwrite an existing file is to call
// fopen in create-only mode (mode 'x').  If it succeeds, the file did not
// exist before, and we've successfully created it, meaning we own the
// file.  After that, we can safely copy over our own file.

$filename = 'sourcefile.txt'
$copyname = 'sourcefile_copy.txt'
if ($file = @fopen($copyname, 'x')) {
    // We've successfully created a file, so it's ours.  We'll close
    // our handle.
    if (!@fclose($file)) {
        // There was some problem with our file handle.
        return false;
    }

    // Now we copy over the file we created.
    if (!@copy($filename, $copyname)) {
        // The copy failed, even though we own the file, so we'll clean
        // up by itrying to remove the file and report failure.
        unlink($copyname);
        return false;
    }

    return true;
}

1
你仍然存在竞争条件。但是你走在了正确的轨道上——如果fopen成功,使用fwrite()执行复制操作,并在完成后取消链接源文件。 - Nathan Strong
除了Nathan所说的,还要使用独占锁打开文件,以防止在复制过程中其他程序进行写操作。 - Professor of programming

3
我认为你已经回答了自己的问题 - 在执行复制操作之前检查目标文件是否存在。如果文件已经存在,则跳过复制。
更新:我看到你确实回答了自己的问题。你提到了竞态条件,但是如果你发现文件已经存在,你如何知道:
- 已经存在的文件是你想要复制的文件吗? - 另一个正在复制该文件的进程是否已完成其工作(文件数据已全部传输)? - 另一个正在复制该文件的进程不会失败(留下不完整的文件或删除新文件)?
我认为在设计解决方案时应考虑这些问题。

1
一个蜜獾函数,它不关心竞态条件,但可在跨平台上运行。
function safeCopy($src, $dest) {
    if (is_file($dest) === true) {
        // if the destination file already exists, it will NOT be overwritten.        
        return false;
    }

    if (copy($src, $dest) === false) {
        echo "Failed to copy $src... Permissions correct?\n";
        return false;
    }

    return true;   
}

0

尝试使用link()函数代替copy()

function safe_copy($src, $dest) {
    if (link($src, $dest)) {
        // Link succeeded, remove old name
        unlink($filename);
        return true;
    } else {
        // Link failed; filesystem has not been altered
        return false;
    }
}

很遗憾,这在Windows上不会起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接