在go中复制文件夹

问题描述 投票:0回答:7

有没有一种简单的方法可以在go中复制目录? 我有以下功能:

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

到目前为止没有任何效果,包括 github.com/cf-guardian/guardian/kernel/fileutils 等库

需要注意的一件重要事情是,我需要保留目录结构,包括sourceFolder本身,而不是简单地复制文件夹的所有内容。

go copy directory
7个回答
24
投票

我相信 docker 实现可以被视为处理边缘情况的完整解决方案: https://github.com/moby/moby/blob/master/daemon/graphdriver/copy/copy.go

有以下好处:

  • 不支持的文件类型上升错误
  • 保留权限和所有权
  • 保留扩展属性
  • 保留时间戳

但是由于大量的导入,你的小应用程序变得巨大。

我尝试结合几种解决方案,但使用 stdlib 并且仅适用于 Linux:

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)
    if err != nil {
        return err
    }

    defer in.Close()

    _, 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)
}


10
投票

这个包似乎正是你想做的,试试吧。

自述文件:

err := Copy("your/source/directory", "your/destination/directory")

7
投票

对已经列出的选项不满意,包括使用粗略的库或过于臃肿的库。

就我而言,我选择以老式的方式做事。使用 shell 命令!

import (
    "os/exec"
)

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

    cmd := exec.Command("cp", "--recursive", oldDir, newDir)
    cmd.Run()
}

2
投票

该解决方案递归地复制目录,包括符号链接。尝试使用流在实际复制阶段提高效率。 如果需要的话,处理更多不规则文件也相当容易。

// CopyDir copies the content of src to dst. src should be a full path.
func CopyDir(dst, src string) error {

    return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // copy to this path
        outpath := filepath.Join(dst, strings.TrimPrefix(path, src))

        if info.IsDir() {
            os.MkdirAll(outpath, info.Mode())
            return nil // means recursive
        }

        // handle irregular files
        if !info.Mode().IsRegular() {
            switch info.Mode().Type() & os.ModeType {
            case os.ModeSymlink:
                link, err := os.Readlink(path)
                if err != nil {
                    return err
                }
                return os.Symlink(link, outpath)
            }
            return nil
        }

        // copy contents of regular file efficiently

        // open input
        in, _ := os.Open(path)
        if err != nil {
            return err
        }
        defer in.Close()

        // create output
        fh, err := os.Create(outpath)
        if err != nil {
            return err
        }
        defer fh.Close()

        // make it the same
        fh.Chmod(info.Mode())

        // copy content
        _, err = io.Copy(fh, in)
        return err
    })
}


0
投票

我想出了一个相对较短的答案,它使用

path/filepath
Walk
方法:

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
}

0
投票

这也可能是一个解决方案:

可在 github.com/floscodes/golang-tools 上获取

import (
    "fmt"
    "io/ioutil"
    "os"
)

func CopyDir(src string, dest string) error {

    if dest[:len(src)] == src {
        return fmt.Errorf("Cannot copy a folder into the folder itself!")
    }

    f, err := os.Open(src)
    if err != nil {
        return err
    }

    file, err := f.Stat()
    if err != nil {
        return err
    }
    if !file.IsDir() {
        return fmt.Errorf("Source " + file.Name() + " is not a directory!")
    }

    err = os.Mkdir(dest, 0755)
    if err != nil {
        return err
    }

    files, err := ioutil.ReadDir(src)
    if err != nil {
        return err
    }

    for _, f := range files {

        if f.IsDir() {

            err = CopyDir(src+"/"+f.Name(), dest+"/"+f.Name())
            if err != nil {
                return err
            }

        }

        if !f.IsDir() {

            content, err := ioutil.ReadFile(src + "/" + f.Name())
            if err != nil {
                return err

            }

            err = ioutil.WriteFile(dest+"/"+f.Name(), content, 0755)
            if err != nil {
                return err

            }

        }

    }

    return nil
}

0
投票

考虑到

golang/go
问题 62484 是“可能接受”,Go 1.23(2024 年第 3 季度)将包括:

err = os.CopyFS(destDir, os.DirFS(srcDir))

来自新 API:

// CopyFS copies the file system fsys into the directory dir,
// creating dir if necessary.
//
// Newly created directories and files have their default modes
// according to the current umask, except that the execute bits
// are copied from the file in fsys when creating a local file.
//
// If a file name in fsys does not satisfy filepath.IsLocal,
// an error is returned for that file.
//
// Copying stops at and returns the first error encountered.
func CopyFS(dir string, fsys fs.FS) error
© www.soinside.com 2019 - 2024. All rights reserved.