使用php将一个目录的全部内容复制到另一个目录

167

我试图使用

cp -r

命令将整个目录的内容复制到另一个位置。

copy ("old_location/*.*","new_location/");

但它说找不到流,真的*.*没有找到。

还有其他方法吗?

谢谢 Dave


1
@编辑们:你确定"old_location/."只是一个打字错误吗? - Felix Kling
Rich Rodecker在他的博客上有一个脚本,似乎可以做到这一点。http://www.visible-form.com/blog/copy-directory-in-php/ - Jon F Hancock
@Felix:我也在想同样的问题。我回滚到了第一个版本,但它有“old_location/*.*”。我找不到包含“old_location/.”的版本。 - Asaph
@Asaph:你的回滚没问题,看看历史记录... 我的意思是 copy ("old_location/.","new_location/"); - Felix Kling
3
@dave 你什么时候会接受呢? - Nam G VU
18个回答

260

这个方法适用于只有一层文件夹的情况。对于包含多级目录的文件夹,我使用了以下方法:

function recurseCopy(
    string $sourceDirectory,
    string $destinationDirectory,
    string $childFolder = ''
): void {
    $directory = opendir($sourceDirectory);

    if (is_dir($destinationDirectory) === false) {
        mkdir($destinationDirectory);
    }

    if ($childFolder !== '') {
        if (is_dir("$destinationDirectory/$childFolder") === false) {
            mkdir("$destinationDirectory/$childFolder");
        }

        while (($file = readdir($directory)) !== false) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            if (is_dir("$sourceDirectory/$file") === true) {
                recurseCopy("$sourceDirectory/$file", "$destinationDirectory/$childFolder/$file");
            } else {
                copy("$sourceDirectory/$file", "$destinationDirectory/$childFolder/$file");
            }
        }

        closedir($directory);

        return;
    }

    while (($file = readdir($directory)) !== false) {
        if ($file === '.' || $file === '..') {
            continue;
        }

        if (is_dir("$sourceDirectory/$file") === true) {
            recurseCopy("$sourceDirectory/$file", "$destinationDirectory/$file");
        }
        else {
            copy("$sourceDirectory/$file", "$destinationDirectory/$file");
        }
    }

    closedir($directory);
}

2
这是一个星号而不是一个星星 ;) - Gordon
2
为什么要使用@mkdir而不是mkdir - Oliboy50
3
你可以询问5年前编写代码的人:http://php.net/manual/en/function.copy.php#91010。也许当时压制错误信息更受欢迎。 - Felix Kling
1
@Oliboy50:我明白了。我认为它会抑制任何错误消息。不过我从来没有真正使用过它。这是文档链接:http://us3.php.net/manual/en/language.operators.errorcontrol.php - Felix Kling
1
@Oliboy50:@ 抑制所有错误 - 许多内置函数不会抛出异常,而是引发无法捕获的错误。通常抑制错误是不好的做法,但文件系统依赖于操作系统和权限,并且有大量可能的故障,因此该运算符在通用、可移植的函数中是可以接受的。 - PeerBr
显示剩余3条评论

96

正如这里所描述的,这是另一种处理符号链接的方法:

/**
 * Copy a file, or recursively copy a folder and its contents
 * @author      Aidan Lister <aidan@php.net>
 * @version     1.0.1
 * @link        http://aidanlister.com/2004/04/recursively-copying-directories-in-php/
 * @param       string   $source    Source path
 * @param       string   $dest      Destination path
 * @param       int      $permissions New folder creation permissions
 * @return      bool     Returns true on success, false on failure
 */
function xcopy($source, $dest, $permissions = 0755)
{
    $sourceHash = hashDirectory($source);
    // Check for symlinks
    if (is_link($source)) {
        return symlink(readlink($source), $dest);
    }

    // Simple copy for a file
    if (is_file($source)) {
        return copy($source, $dest);
    }

    // Make destination directory
    if (!is_dir($dest)) {
        mkdir($dest, $permissions);
    }

    // Loop through the folder
    $dir = dir($source);
    while (false !== $entry = $dir->read()) {
        // Skip pointers
        if ($entry == '.' || $entry == '..') {
            continue;
        }

        // Deep copy directories
        if($sourceHash != hashDirectory($source."/".$entry)){
             xcopy("$source/$entry", "$dest/$entry", $permissions);
        }
    }

    // Clean up
    $dir->close();
    return true;
}

// In case of coping a directory inside itself, there is a need to hash check the directory otherwise and infinite loop of coping is generated

function hashDirectory($directory){
    if (! is_dir($directory)){ return false; }

    $files = array();
    $dir = dir($directory);

    while (false !== ($file = $dir->read())){
        if ($file != '.' and $file != '..') {
            if (is_dir($directory . '/' . $file)) { $files[] = hashDirectory($directory . '/' . $file); }
            else { $files[] = md5_file($directory . '/' . $file); }
        }
    }

    $dir->close();

    return md5(implode('', $files));
}

成功地复制了一个包含140个子文件夹的文件夹,每个子文件夹都包含21张图片。非常好用!谢谢! - Darksaint2014
2
应该在mkdir命令的最后一个参数中添加true以支持递归目录,然后此脚本就完美了。 - ZenithS
这会复制整个文件夹吗?如果你只想复制文件夹内部的文件,而不包括父文件夹,就像问题所说的那样:copy ("old_location/*.*","new_location/"); 这样可以吗?如果有点文件,它们会被匹配吗? - XCS

41

copy() 只适用于文件。

DOS 的 copy 和 Unix 的 cp 命令都支持递归拷贝,因此最快的解决方案就是调用这些命令。例如:

`cp -r $src $dest`;
否则,您需要使用opendir/readdirscandir来读取目录内容,遍历结果,并且如果每个结果返回true的话,则递归进入其中。

例如:

function xcopy($src, $dest) {
    foreach (scandir($src) as $file) {
        if (!is_readable($src . '/' . $file)) continue;
        if (is_dir($src .'/' . $file) && ($file != '.') && ($file != '..') ) {
            mkdir($dest . '/' . $file);
            xcopy($src . '/' . $file, $dest . '/' . $file);
        } else {
            copy($src . '/' . $file, $dest . '/' . $file);
        }
    }
}

5
以下是一个更稳定、更干净的xcopy()版本,如果文件夹存在则不会创建该文件夹:`function xcopy($src, $dest) { foreach (scandir($src) as $file) { $srcfile = rtrim($src, '/') .'/'. $file; $destfile = rtrim($dest, '/') .'/'. $file; if (!is_readable($srcfile)) { continue; } if ($file != '.' && $file != '..') { if (is_dir($srcfile)) { if (!file_exists($destfile)) { mkdir($destfile); } xcopy($srcfile, $destfile); } else { copy($srcfile, $destfile); } } } }` - TheStoryCoder
感谢提供反引号解决方案!这是一篇帮助我调整复制命令的页面:UNIX cp explained。另外,PHP >=5.3 提供了一些不错的迭代器 - maxpower9000

25

最好的解决方案是!

<?php
$src = "/home/www/domain-name.com/source/folders/123456";
$dest = "/home/www/domain-name.com/test/123456";

shell_exec("cp -r $src $dest");

echo "<H3>Copy Paste completed!</H3>"; //output when done
?>

37
无法在Windows服务器或其他无法访问shell_execcp的环境中工作。这使得它在我看来很难成为“最佳”解决方案。 - Pelle
3
除此之外,当有人找到在 PHP 文件中通过命令行控制时,这将成为一个严重的问题,因为此人可能会得到服务器上的文件。 - Martijn
运行得很好!在CentOS上表现很棒。谢谢@bstpierre - Nick Green
1
这个在Windows上根本行不通,因为cp是Linux命令。在Windows上使用xcopy dir1 dir2 /e /i,其中/e表示复制空目录,/i表示忽略有关文件或目录的问题。 - Michel
只有在您确定环境时才建议这样做。在共享主机中,出于安全原因,大多数情况下都会禁用它。 - Khalilullah
显示剩余2条评论

22

16
function full_copy( $source, $target ) {
    if ( is_dir( $source ) ) {
        @mkdir( $target );
        $d = dir( $source );
        while ( FALSE !== ( $entry = $d->read() ) ) {
            if ( $entry == '.' || $entry == '..' ) {
                continue;
            }
            $Entry = $source . '/' . $entry; 
            if ( is_dir( $Entry ) ) {
                full_copy( $Entry, $target . '/' . $entry );
                continue;
            }
            copy( $Entry, $target . '/' . $entry );
        }

        $d->close();
    }else {
        copy( $source, $target );
    }
}

完美运行!谢谢兄弟。 - Robin Delaporte

7

如其他地方所述,copy仅适用于单个文件的源而不是模式。如果要按模式复制,请使用glob确定文件,然后运行复制。但这样做不会复制子目录,也不会创建目标目录。

function copyToDir($pattern, $dir)
{
    foreach (glob($pattern) as $file) {
        if(!is_dir($file) && is_readable($file)) {
            $dest = realpath($dir . DIRECTORY_SEPARATOR) . basename($file);
            copy($file, $dest);
        }
    }    
}
copyToDir('./test/foo/*.txt', './test/bar'); // copies all txt files

考虑将以下代码更改为: $dest = realpath($dir ) . DIRECTORY_SEPARATOR . basename($file); 而不是: $dest = realpath($dir . DIRECTORY_SEPARATOR) . basename($file); - dawez
据我所记,glob 会跳过以点(.)开头的隐藏文件。 - Svetoslav Marinov
@SvetoslavMarinov 是的,默认情况下是这样的,但是如果我没记错的话,glob('{,.}*', GLOB_BRACE); 也应该考虑到隐藏文件(不过我已经有一段时间没有用过了)。 - Gordon

7
<?php
    function copy_directory( $source, $destination ) {
        if ( is_dir( $source ) ) {
        @mkdir( $destination );
        $directory = dir( $source );
        while ( FALSE !== ( $readdirectory = $directory->read() ) ) {
            if ( $readdirectory == '.' || $readdirectory == '..' ) {
                continue;
            }
            $PathDir = $source . '/' . $readdirectory; 
            if ( is_dir( $PathDir ) ) {
                copy_directory( $PathDir, $destination . '/' . $readdirectory );
                continue;
            }
            copy( $PathDir, $destination . '/' . $readdirectory );
        }

        $directory->close();
        }else {
        copy( $source, $destination );
        }
    }
?>

从第四行开始,将其变成这样

$source = 'wordpress';//i.e. your source path

并且

$destination ='b';

6

非常感谢 Felix Kling 提供的出色答案,我已经在我的代码中感激地使用了它。 我提供了一项小增强功能,用于报告成功或失败的布尔返回值:

function recurse_copy($src, $dst) {

  $dir = opendir($src);
  $result = ($dir === false ? false : true);

  if ($result !== false) {
    $result = @mkdir($dst);

    if ($result === true) {
      while(false !== ( $file = readdir($dir)) ) { 
        if (( $file != '.' ) && ( $file != '..' ) && $result) { 
          if ( is_dir($src . '/' . $file) ) { 
            $result = recurse_copy($src . '/' . $file,$dst . '/' . $file); 
          }     else { 
            $result = copy($src . '/' . $file,$dst . '/' . $file); 
          } 
        } 
      } 
      closedir($dir);
    }
  }

  return $result;
}

1
你正在使用 recurse_copy() 和 recurseCopy() 作为函数名,请更新它。 - AgelessEssence
closedir($dir);语句需要放在if($result===true)语句之外,即再往下一个花括号。否则会存在未释放的资源风险。 - Dimitar Darazhanski

5

以下是我简化了@Kzoty的回答,感谢Kzoty。

用法

Helper::copy($sourcePath, $targetPath);

class Helper {

    static function copy($source, $target) {
        if (!is_dir($source)) {//it is a file, do a normal copy
            copy($source, $target);
            return;
        }

        //it is a folder, copy its files & sub-folders
        @mkdir($target);
        $d = dir($source);
        $navFolders = array('.', '..');
        while (false !== ($fileEntry=$d->read() )) {//copy one by one
            //skip if it is navigation folder . or ..
            if (in_array($fileEntry, $navFolders) ) {
                continue;
            }

            //do copy
            $s = "$source/$fileEntry";
            $t = "$target/$fileEntry";
            self::copy($s, $t);
        }
        $d->close();
    }

}

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