Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy a folder in go

Tags:

directory

copy

go

Is there an easy way to copy a directory in go? I have the following function:

err = CopyDir("sourceFolder","destinationFolder")

Nothing so far has worked, including libraries such as github.com/cf-guardian/guardian/kernel/fileutils

One important thing to note is that I need to preserve directory structure, including the sourceFolder itself, not simply copy all contents of the folder.

like image 382
P A S H Avatar asked Aug 10 '18 04:08

P A S H


People also ask

How do I copy an entire folder?

Alternatively, right-click the folder, select Show more options and then Copy. In Windows 10 and earlier versions, right-click the folder and select Copy, or click Edit and then Copy. Navigate to the location where you want to place the folder and all its contents.

How do I copy a folder and subfolders?

Type "xcopy", "source", "destination" /t /e in the Command Prompt window. Instead of “ source ,” type the path of the folder hierarchy you want to copy. Instead of “ destination ,” enter the path where you want to store the copied folder structure. Press “Enter” on your keyboard.


4 Answers

I believe that docker implementation can be considered as complete solution for handling edge cases: https://github.com/moby/moby/blob/master/daemon/graphdriver/copy/copy.go

There are following good things:

  • unsupported file type rise error
  • preserving permissions and ownership
  • preserving extended attributes
  • preserving timestamp

but because of a lot of imports your tiny application becomes huge.

I've tried to combine several solutions but use stdlib and for Linux only:

func CopyDirectory(scrDir, dest string) error {
    entries, err := os.ReadDir(scrDir)
    if err != nil {
        return err
    }
    for _, entry := range entries {
        sourcePath := filepath.Join(scrDir, entry.Name())
        destPath := filepath.Join(dest, entry.Name())

        fileInfo, err := os.Stat(sourcePath)
        if err != nil {
            return err
        }

        stat, ok := fileInfo.Sys().(*syscall.Stat_t)
        if !ok {
            return fmt.Errorf("failed to get raw syscall.Stat_t data for '%s'", sourcePath)
        }

        switch fileInfo.Mode() & os.ModeType{
        case os.ModeDir:
            if err := CreateIfNotExists(destPath, 0755); err != nil {
                return err
            }
            if err := CopyDirectory(sourcePath, destPath); err != nil {
                return err
            }
        case os.ModeSymlink:
            if err := CopySymLink(sourcePath, destPath); err != nil {
                return err
            }
        default:
            if err := Copy(sourcePath, destPath); err != nil {
                return err
            }
        }

        if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
            return err
        }

        fInfo, err := entry.Info()
        if err != nil {
            return err
        }

        isSymlink := fInfo.Mode()&os.ModeSymlink != 0
        if !isSymlink {
            if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
                return err
            }
        }
    }
    return nil
}

func Copy(srcFile, dstFile string) error {
    out, err := os.Create(dstFile)
    if err != nil {
        return err
    }

    defer out.Close()

    in, err := os.Open(srcFile)
    defer in.Close()
    if err != nil {
        return err
    }

    _, err = io.Copy(out, in)
    if err != nil {
        return err
    }

    return nil
}

func Exists(filePath string) bool {
    if _, err := os.Stat(filePath); os.IsNotExist(err) {
        return false
    }

    return true
}

func CreateIfNotExists(dir string, perm os.FileMode) error {
    if Exists(dir) {
        return nil
    }

    if err := os.MkdirAll(dir, perm); err != nil {
        return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
    }

    return nil
}

func CopySymLink(source, dest string) error {
    link, err := os.Readlink(source)
    if err != nil {
        return err
    }
    return os.Symlink(link, dest)
}

like image 199
Oleg Neumyvakin Avatar answered Oct 18 '22 00:10

Oleg Neumyvakin


This package seems to do exactly what you want to do, give it a try.

From the readme:

err := Copy("your/source/directory", "your/destination/directory")
like image 31
Ullaakut Avatar answered Oct 18 '22 00:10

Ullaakut


Not satisfied with the already listed options which include using sketchy libraries, or vastly bloated libraries.

In my case, I opted to do things the old fashioned way. With shell commands!

import (
    "os/exec"
)

func main() {
    // completely arbitrary paths
    oldDir := "/home/arshbot/"
    newDir := "/tmp/"

    cmd := exec.Command("cp", "--recursive", oldDir, newDir)
    cmd.Run()
}
like image 22
arshbot Avatar answered Oct 18 '22 00:10

arshbot


I've come up with a relatively shorter answer which uses path/filepath's Walk method:

import (
    "io/ioutil"
    "path/filepath"
    "os"
    "strings"
)

func copy(source, destination string) error {
    var err error = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
        var relPath string = strings.Replace(path, source, "", 1)
        if relPath == "" {
            return nil
        }
        if info.IsDir() {
            return os.Mkdir(filepath.Join(destination, relPath), 0755)
        } else {
            var data, err1 = ioutil.ReadFile(filepath.Join(source, relPath))
            if err1 != nil {
                return err1
            }
            return ioutil.WriteFile(filepath.Join(destination, relPath), data, 0777)
        }
    })
    return err
}
like image 43
Jonathan Avatar answered Oct 17 '22 23:10

Jonathan