166

I tried to copy the entire contents of the directory to another location using

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

but it says it cannot find stream, true *.* is not found.

Any other way

Thanks Dave

Asaph
  • 159,146
  • 25
  • 197
  • 199
dave
  • 1,715
  • 3
  • 13
  • 8

18 Answers18

258

that worked for a one level directory. for a folder with multi-level directories I used this:

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);
}
Max
  • 374
  • 1
  • 4
  • 10
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 2
    It's an asterisk and not a star ;) – Gordon Jan 12 '10 at 17:26
  • 2
    Why `@mkdir` instead of `mkdir` ? – Oliboy50 Jul 11 '14 at 09:10
  • 3
    @Oliboy50: You can ask the person who wrote the code 5 years ago: http://php.net/manual/en/function.copy.php#91010. Maybe it was more popular back then to suppress error messages. – Felix Kling Jul 11 '14 at 09:12
  • Actually I didn't know what the `@` means in PHP (so I thought it was a different function), but I think it's OK know (it removes warnings, right ?) – Oliboy50 Jul 11 '14 at 09:16
  • 1
    @Oliboy50: I see. I think it suppresses any error message. I never really used it though. This is the documentation: http://us3.php.net/manual/en/language.operators.errorcontrol.php – Felix Kling Jul 11 '14 at 09:18
  • this worked great for a folder containing only files, but failed to copy any sub directories in the folder – Darksaint2014 Sep 18 '14 at 14:49
  • @Darksaint2014: Mmh, it should work because of `if ( is_dir($src . '/' . $file) ) {`. However, if you debug it and tell me what the problem is, I will gladly update the code. – Felix Kling Sep 18 '14 at 14:56
  • 1
    @Oliboy50: The @ suppresses all errors - many built-in functions do not throw exceptions but raise errors which cannot be caught. It is usually bad practice to suppress errors, but the file system depends on the OS and permissions and has tons of possible failures, so that operator is ok in a generic, portable function. – PeerBr Sep 26 '14 at 11:05
96

As described here, this is another approach that takes care of symlinks too:

/**
 * 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));
}
itsjavi
  • 2,574
  • 2
  • 21
  • 24
  • Worked great to copy a folder with 140 sub folders and each subfolder containing 21 images. Works great! Thanks! – Darksaint2014 Sep 18 '14 at 14:47
  • 2
    `mkdir` should be added `true` as the last parameter to support recursively directory then this script is perfect – ZenithS Oct 22 '18 at 03:46
  • This copies the entire folder? What if you only want to copy the files **inside** the folder, without the parent folder, as the question says: `copy ("old_location/*.*","new_location/");` Does that work? What if you have dot files, will they be matched? – XCS Jul 14 '20 at 23:52
41

copy() only works with files.

Both the DOS copy and Unix cp commands will copy recursively - so the quickest solution is just to shell out and use these. e.g.

`cp -r $src $dest`;

Otherwise you'll need to use the opendir/readdir or scandir to read the contents of the directory, iterate through the results and if is_dir returns true for each one, recurse into it.

e.g.

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);
        }
    }
}
symcbean
  • 47,736
  • 6
  • 59
  • 94
  • 5
    Here is a more stable and cleaner version of xcopy() which does not create the folder if it exists: `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 Nov 10 '13 at 10:03
  • Thanks for the **backtick solution**! A page that helped me tweak the copy command: [UNIX cp explained](http://www.computerhope.com/unix/ucp.htm). Additional info: PHP >=5.3 offers some nice [iterators](http://de2.php.net/manual/en/class.recursivedirectoryiterator.php) – maxpower9000 Nov 06 '15 at 09:47
25

The best solution is!

<?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
?>
bstpierre
  • 30,042
  • 15
  • 70
  • 103
black
  • 339
  • 3
  • 2
  • 37
    Won't work on Windows servers or other environments where you either have no access to either `shell_exec` or `cp`. That makes it - in my opinion - hardly the "best" solution. – Pelle Aug 02 '12 at 14:21
  • 3
    Apart from that, commandline controls from a PHP file can be a big problem when someone find a way to get a file on your server. – Martijn Feb 21 '14 at 13:41
  • Worked like a charm! On CentOS and it worked great. Thanks @bstpierre – Nick Green Oct 12 '16 at 07:50
  • 1
    This won't work on Windows at all, because `cp` is a Linux command. For Windows use `xcopy dir1 dir2 /e /i` , where `/e` stands for copy empty dirs and `/i` for ignore questions about files or directories – Michel Jun 22 '18 at 08:20
  • Yes, it is the best solution if your server supports this command and you have required permissions. It is very fast. Unfortunately not working all environments. – mdikici Apr 02 '19 at 10:41
  • This is only recommended when you are sure about the environment. Most of the time in shared hosting, it's disabled for security reasons. – Khalilullah Mar 03 '21 at 03:32
  • With the recursive "-r" option, you need to specify the source directory followed by a wildcard character: "cp -r $src/* $dest" – Eric P May 24 '22 at 07:34
21

With Symfony this is very easy to accomplish:

$fileSystem = new Symfony\Component\Filesystem\Filesystem();
$fileSystem->mirror($from, $to);

See https://symfony.com/doc/current/components/filesystem.html

chickenchilli
  • 3,358
  • 2
  • 23
  • 24
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 );
    }
}
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
kzoty
  • 169
  • 1
  • 2
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 );
        }
    }
?>

from the last 4th line , make this

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

and

$destination ='b';
Guern
  • 1,237
  • 1
  • 12
  • 30
Hemanta Nandi
  • 141
  • 1
  • 4
7

Like said elsewhere, copy only works with a single file for source and not a pattern. If you want to copy by pattern, use glob to determine the files, then run copy. This will not copy subdirectories though, nor will it create the destination directory.

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
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • consider changing: $dest = realpath($dir . DIRECTORY_SEPARATOR) . basename($file); with: $dest = realpath($dir ) . DIRECTORY_SEPARATOR . basename($file); – dawez Mar 19 '13 at 10:34
  • as far as I remember glob would skip hidden files (starting with a dot .) – Svetoslav Marinov Feb 17 '23 at 15:31
  • @SvetoslavMarinov yes, by default, but `glob('{,.}*', GLOB_BRACE);` should also consider the hidden files if I recall correctly (it's been a awhile though) – Gordon Feb 20 '23 at 13:06
6

Full thanks must go to Felix Kling for his excellent answer which I have gratefully used in my code. I offer a small enhancement of a boolean return value to report success or failure:

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;
}
smichaelsen
  • 158
  • 8
gonzo
  • 526
  • 6
  • 6
5

My pruned version of @Kzoty answer. Thank you Kzoty.

Usage

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();
    }

}
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
2

I clone entire directory by SPL Directory Iterator.

function recursiveCopy($source, $destination)
{
    if (!file_exists($destination)) {
        mkdir($destination);
    }

    $splFileInfoArr = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

    foreach ($splFileInfoArr as $fullPath => $splFileinfo) {
        //skip . ..
        if (in_array($splFileinfo->getBasename(), [".", ".."])) {
            continue;
        }
        //get relative path of source file or folder
        $path = str_replace($source, "", $splFileinfo->getPathname());

        if ($splFileinfo->isDir()) {
            mkdir($destination . "/" . $path);
        } else {
        copy($fullPath, $destination . "/" . $path);
        }
    }
}
#calling the function
recursiveCopy(__DIR__ . "/source", __DIR__ . "/destination");
Tuhin Bepari
  • 725
  • 5
  • 11
2

For Linux servers you just need one line of code to copy recursively while preserving permission:

exec('cp -a '.$source.' '.$dest);

Another way of doing it is:

mkdir($dest);
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item)
{
    if ($item->isDir())
        mkdir($dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName());
    else
        copy($item, $dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName());
}

but it's slower and does not preserve permissions.

Dan Bray
  • 7,242
  • 3
  • 52
  • 70
1
// using exec

function rCopy($directory, $destination)
{

    $command = sprintf('cp -r %s/* %s', $directory, $destination);

    exec($command);

}
0

I had a similar situation where I needed to copy from one domain to another on the same server, Here is exactly what worked in my case, you can as well adjust to suit yours:

foreach(glob('../folder/*.php') as $file) {
$adjust = substr($file,3);
copy($file, '/home/user/abcde.com/'.$adjust);

Notice the use of "substr()", without it, the destination becomes '/home/user/abcde.com/../folder/', which might be something you don't want. So, I used substr() to eliminate the first 3 characters(../) in order to get the desired destination which is '/home/user/abcde.com/folder/'. So, you can adjust the substr() function and also the glob() function until it fits your personal needs. Hope this helps.

Chimdi
  • 303
  • 2
  • 7
0

Long-winded, commented example with return logging, based on parts of most of the answers here:

It is presented as a static class method, but could work as a simple function also:

/**
 * Recursive copy directories and content
 * 
 * @link        https://stackoverflow.com/a/2050909/591486
 * @since       4.7.2
*/
public static function copy_recursive( $source = null, $destination = null, &$log = [] ) {

    // is directory ##
    if ( is_dir( $source ) ) {

        $log[] = 'is_dir: '.$source;

        // log results of mkdir call ##
        $log[] = '@mkdir( "'.$destination.'" ): '.@mkdir( $destination );

        // get source directory contents ##
        $source_directory = dir( $source );

        // loop over items in source directory ##
        while ( FALSE !== ( $entry = $source_directory->read() ) ) {
            
            // skip hidden ##
            if ( $entry == '.' || $entry == '..' ) {

                $log[] = 'skip hidden entry: '.$entry;

                continue;

            }

            // get full source "entry" path ##
            $source_entry = $source . '/' . $entry; 

            // recurse for directories ##
            if ( is_dir( $source_entry ) ) {

                $log[] = 'is_dir: '.$source_entry;

                // return to self, with new arguments ##
                self::copy_recursive( $source_entry, $destination.'/'.$entry, $log );

                // break out of loop, to stop processing ##
                continue;

            }

            $log[] = 'copy: "'.$source_entry.'" --> "'.$destination.'/'.$entry.'"';

            // copy single files ##
            copy( $source_entry, $destination.'/'.$entry );

        }

        // close connection ##
        $source_directory->close();

    } else {

        $log[] = 'copy: "'.$source.'" --> "'.$destination.'"';

        // plain copy, as $destination is a file ##
        copy( $source, $destination );

    }

    // clean up log ##
    $log = array_unique( $log );

    // kick back log for debugging ##
    return $log;

}

Call like:

// call method ##
$log = \namespace\to\method::copy_recursive( $source, $destination );

// write log to error file - you can also just dump it on the screen ##
error_log( var_export( $log, true ) );
Q Studio
  • 1,811
  • 3
  • 16
  • 18
0

For copy entire contents from a directory to another, first you should sure about transfer files that they were transfer correctly. for this reason, we use copy files one by one! in correct directories. we copy a file and check it if true go to next file and continue...

1- I check the safe process of transferring each file with this function:


function checksum($src,$dest)
{
    if(file_exists($src) and file_exists($dest)){
        return md5_file($src) == md5_file($dest) ? true : false;
    }else{
        return false;
    }
}

2- Now i copy files one by one from src into dest, check it and then continue. (For separate the folders that i don't want to copy them, use exclude array)

$src  = __DIR__ . '/src';
$dest = __DIR__ . '/dest';
$exclude = ['.', '..'];

function copyDir($src, $dest, $exclude)
{

    !is_dir($dest) ? mkdir($dest) : '';

    foreach (scandir($src) as $item) {

        $srcPath = $src . '/' . $item;
        $destPath = $dest . '/' . $item;

        if (!in_array($item, $exclude)) {

            if (is_dir($srcPath)) {

                copyDir($srcPath, $destPath, $exclude);

            } else {

                copy($srcPath, $destPath);

                if (checksum($srcPath, $destPath)) {
                    echo 'Success transfer for:' . $srcPath . '<br>';
                }else{
                    echo 'Failed transfer for:' . $srcPath . '<br>';
                }
            }
        }
    }

}
  • Code without any explanation are rarely helpful. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please edit your question and explain how it answers the specific question being asked. See [How to Answer](https://stackoverflow.com/help/how-to-answer). – Sfili_81 Jun 21 '21 at 13:59
0

I find this to be way simpler, more easily customizable, and to not require any dependency:

foreach(glob("old_location/*") as $file) {
    copy($file, "new_location/" . basename($file));
}
Vic Seedoubleyew
  • 9,888
  • 6
  • 55
  • 76
0

If your server accepts Shell commands - Use a shell commend and include the ampersand (&) - the shell copy command will execute and proceed independently of you PHP script, and even continue the copy process long after your script is complete; in the case of a large directory copy process.

If your only desire is to copy what is there exactly to another directory - this is the fastest and least interruptive method.

<?php

    $src = "my_path_to_folder";
    $dst = "my_destination_folder";
    $command = 'cp -a ' . $src . ' ' .$dst;
    shell_exec(escapeshellcmd($command).' &');

?>
TV-C-1-5
  • 680
  • 2
  • 13
  • 19