Copying folder including subfolders

Is there an easy way to copy the contents of one folder, including all subfolders, to another folder?

It seems that cp() supported the recursive keyword at one point, but not anymore.

1 Like

Base.Filesystem.cptree("test", "test_copy")

Thanks, but when the destination folder dst already exists, cptree() asks me to use the keyword argument force=true. This seems to remove the dst folder and then recreate it, instead of simply overwriting its contents. The dst folder contains extra files that don’t exist in src, and I want to keep them. Is there a way to overwrite the contents of dst without removing dst itself and recreating it?

5 Likes

I am also looking for the equivalent of bash’s cp command in julia. couldn’t find anything so far

Write a function that suits your needs yourself. So easy in Julia.

same holds for mv command and some others as well probably.
It is surprising that the julia version is called the same but so different from the shell version…

It would be great if all those commands get a flag to switch to shell recursive behaviour.

Custom recursive behaviour on a folder structure can be obtained by using the walkdir iterator.

a nice trick when using walkdir is using something like destdir = normpath(joinpath(dst, relpath(root, src))) to obtain a mirror copy of the folder structure but starting from dst instead of src.

If the current folder being walked is src/some/folder (the root in the snippet above) you can check existence of dst/some/folder before doing any action. You can then check existence of single files: isfile(joinpath(destdir, file)) where file is from the files list obtained from the walkdir iterator.

one especially handy feature of walkdir is that you can just invert it and use a bottom up approach.

For reference, here is one approach:

function cpdir(srcdir,dstdir;
               matching = (entry) -> true,
               matching_dir = matching,
               matching_file = matching)
    mkpath(dstdir)
    for f in readdir(srcdir)
        src_entry = joinpath(srcdir,f)
        dst_entry = joinpath(dstdir,f)
        if isfile(src_entry)
            if matching_file(f)
                cp(src_entry,dst_entry)
            end
        elseif isdir(src_entry)
            if matching_dir(f)
                # recursive call
                cpdir(src_entry,dst_entry;
                      matching, matching_dir, matching_file)
            end
        end
    end
end

Copying all directory srcdir to dstdir, here ignoring the .git directory and files ending with .out and ~:

using Glob: ismatch, @fn_str
cpdir(srcdir,dstdir;
      matching_dir = !=(".git"),
      matching_file = e -> !ismatch(fn"*.out",e) && !ismatch(fn"*~",e))

Or without Glob

cpdir(srcdir,dstdir;                                                                                                                                                                
          matching_dir = !=(".git"),                                                                                                                                                    
          matching_file = e -> !endswith(e,".out") && !endswith(e,"~"))  

As @rafael.guerra mentioned, this code will not overwrite any files, but returns with an ArgumentError if a file already exists. See Rafaels’ comment below.

1 Like

@Alexander-Barth, I tried your code on a Win11 PC but I get the error:

ERROR: ArgumentError: '.\destdir\file1.txt' exists. `force=true` is required to remove '.\destdir\file1.txt' before copying.

Adding the keyword force=true to the cp() function, it will replace the files in the destination folder & subfolders with those with the same name from the source folder & subfolders (which is OK, but worth mentioning), while preserving those that do not match.

On another note, your code looks in the subdirectories, but I cannot see how. Could you give me a hint? Cheers.

1 Like

cpdir() has a recursive call.

2 Likes

Thanks @healyp, I totally overlooked that.