Simple way to copy a file

huangapple go评论81阅读模式
英文:

Simple way to copy a file

问题

在Go语言中,有没有一种简单快捷的方法来复制文件?

我在文档中找不到快速的方法,而且在互联网上搜索也没有帮助。

英文:

Is there any simple/fast way to copy a file in Go?

I couldn't find a fast way in the Doc's and searching the internet doesn't help as well.

答案1

得分: 79

警告:这个答案主要是关于如何向文件添加硬链接,而不是关于复制内容。

一个稳健且高效的复制操作在概念上很简单,但由于需要处理一些边缘情况和系统限制,实现起来并不简单,这些限制是由目标操作系统及其配置所决定的。

如果你只是想创建现有文件的副本,可以使用 os.Link(srcName, dstName)。这样可以避免在应用程序中移动字节,并节省磁盘空间。对于大文件来说,这是一个显著的时间和空间节省。

但是不同的操作系统对硬链接的工作方式有不同的限制。根据你的应用程序和目标系统配置,Link() 调用可能在某些情况下无法正常工作。

如果你想要一个通用的、稳健的和高效的复制函数,可以更新 Copy() 函数:

  1. 进行检查,确保至少某种形式的复制将成功(访问权限、目录存在等)。
  2. 使用 os.SameFile 检查两个文件是否已经存在且相同,如果相同则返回成功。
  3. 尝试创建硬链接,如果成功则返回。
  4. 复制字节(所有高效的手段都失败了),返回结果。

一个优化的方法是在一个 go routine 中复制字节,这样调用者就不会在字节复制上阻塞。但这样做会给调用者带来额外的复杂性,需要正确处理成功/错误的情况。

如果我需要这两个功能,我会有两个不同的复制函数:CopyFile(src, dst string) (error) 用于阻塞复制,CopyFileAsync(src, dst string) (chan c, error) 用于异步复制,并将一个信号通道传递回给调用者。

以下是示例代码:

package main

import (
	"fmt"
	"io"
	"os"
)

// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherwise, attempt to create a hard link
// between the two files. If that fails, copy the file contents from src to dst.
func CopyFile(src, dst string) (err error) {
	sfi, err := os.Stat(src)
	if err != nil {
		return
	}
	if !sfi.Mode().IsRegular() {
		// 无法复制非常规文件(例如目录、符号链接、设备等)
		return fmt.Errorf("CopyFile: 非常规源文件 %s (%q)", sfi.Name(), sfi.Mode().String())
	}
	dfi, err := os.Stat(dst)
	if err != nil {
		if !os.IsNotExist(err) {
			return
		}
	} else {
		if !dfi.Mode().IsRegular() {
			return fmt.Errorf("CopyFile: 非常规目标文件 %s (%q)", dfi.Name(), dfi.Mode().String())
		}
		if os.SameFile(sfi, dfi) {
			return
		}
	}
	if err = os.Link(src, dst); err == nil {
		return
	}
	err = copyFileContents(src, dst)
	return
}

// copyFileContents 将名为 src 的文件的内容复制到名为 dst 的文件中。
// 如果目标文件不存在,将创建它。如果目标文件存在,它的所有内容将被源文件的内容替换。
func copyFileContents(src, dst string) (err error) {
	in, err := os.Open(src)
	if err != nil {
		return
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return
	}
	err = out.Sync()
	return
}

func main() {
	fmt.Printf("将 %s 复制到 %s\n", os.Args[1], os.Args[2])
	err := CopyFile(os.Args[1], os.Args[2])
	if err != nil {
		fmt.Printf("CopyFile 失败:%q\n", err)
	} else {
		fmt.Printf("CopyFile 成功\n")
	}
}

希望对你有帮助!

英文:

> Warning: This answer is mainly about adding a hard link to a file, not about copying the contents.

A robust and efficient copy is conceptually simple, but not simple to implement due to the need to handle a number of edge cases and system limitations that are imposed by the target operating system and it's configuration.

If you simply want to make a duplicate of the existing file you can use os.Link(srcName, dstName). This avoids having to move bytes around in the application and saves disk space. For large files, this is a significant time and space saving.

But various operating systems have different restrictions on how hard links work. Depending on your application and your target system configuration, Link() calls may not work in all cases.

If you want a single generic, robust and efficient copy function, update Copy() to:

  1. Perform checks to ensure that at least some form of copy will succeed (access permissions, directories exist, etc.)
  2. Check to see if both files already exist and are the same using
    os.SameFile, return success if they are the same
  3. Attempt a Link, return if success
  4. Copy the bytes (all efficient means failed), return result

An optimization would be to copy the bytes in a go routine so the caller doesn't block on the byte copy. Doing so imposes additional complexity on the caller to handle the success/error case properly.

If I wanted both, I would have two different copy functions: CopyFile(src, dst string) (error) for a blocking copy and CopyFileAsync(src, dst string) (chan c, error) which passes a signaling channel back to the caller for the asynchronous case.

<!-- language:go -->

package main
import (
&quot;fmt&quot;
&quot;io&quot;
&quot;os&quot;
)
// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherise, attempt to create a hard link
// between the two files. If that fail, copy the file contents from src to dst.
func CopyFile(src, dst string) (err error) {
sfi, err := os.Stat(src)
if err != nil {
return
}
if !sfi.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories,
// symlinks, devices, etc.)
return fmt.Errorf(&quot;CopyFile: non-regular source file %s (%q)&quot;, sfi.Name(), sfi.Mode().String())
}
dfi, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return
}
} else {
if !(dfi.Mode().IsRegular()) {
return fmt.Errorf(&quot;CopyFile: non-regular destination file %s (%q)&quot;, dfi.Name(), dfi.Mode().String())
}
if os.SameFile(sfi, dfi) {
return
}
}
if err = os.Link(src, dst); err == nil {
return
}
err = copyFileContents(src, dst)
return
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it&#39;s contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
func main() {
fmt.Printf(&quot;Copying %s to %s\n&quot;, os.Args[1], os.Args[2])
err := CopyFile(os.Args[1], os.Args[2])
if err != nil {
fmt.Printf(&quot;CopyFile failed %q\n&quot;, err)
} else {
fmt.Printf(&quot;CopyFile succeeded\n&quot;)
}
}

答案2

得分: 26

import (
    "io/ioutil"
    "log"
)

func checkErr(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func copy(src string, dst string) {
    // 将src的所有内容读取到data中,对于大文件可能会导致OOM。
    data, err := ioutil.ReadFile(src)
    checkErr(err)
    // 将data写入到dst中
    err = ioutil.WriteFile(dst, data, 0644)
    checkErr(err)
}

以上是给定的代码的中文翻译。

英文:
import (
&quot;io/ioutil&quot;
&quot;log&quot;
)
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func copy(src string, dst string) {
// Read all content of src to data, may cause OOM for a large file.
data, err := ioutil.ReadFile(src)
checkErr(err)
// Write data to dst
err = ioutil.WriteFile(dst, data, 0644)
checkErr(err)
}

答案3

得分: 11

如果你在Linux/Mac上运行代码,你可以直接执行系统的cp命令。

srcFolder := "copy/from/path"
destFolder := "copy/to/path"
cpCmd := exec.Command("cp", "-rf", srcFolder, destFolder)
err := cpCmd.Run()

这段代码将go当作脚本来处理,但它能完成任务。另外,你需要导入"os/exec"。

英文:

If you are running the code in linux/mac, you could just execute the system's cp command.

srcFolder := &quot;copy/from/path&quot;
destFolder := &quot;copy/to/path&quot;
cpCmd := exec.Command(&quot;cp&quot;, &quot;-rf&quot;, srcFolder, destFolder)
err := cpCmd.Run()

It's treating go a bit like a script, but it gets the job done. Also, you need to import "os/exec"

答案4

得分: 6

从Go 1.15(2020年8月)开始,您可以使用File.ReadFrom

package main
import "os"
func main() {
r, err := os.Open("in.txt")
if err != nil {
panic(err)
}
defer r.Close()
w, err := os.Create("out.txt")
if err != nil {
panic(err)
}
defer w.Close()
w.ReadFrom(r)
}
英文:

Starting with Go 1.15 (Aug 2020), you can use File.ReadFrom:

package main
import &quot;os&quot;
func main() {
r, err := os.Open(&quot;in.txt&quot;)
if err != nil {
panic(err)
}
defer r.Close()
w, err := os.Create(&quot;out.txt&quot;)
if err != nil {
panic(err)
}
defer w.Close()
w.ReadFrom(r)
}

答案5

得分: 5

  • 使用io.Copy在流中执行复制操作。
  • 关闭所有打开的文件描述符。
  • 检查所有需要检查的错误,包括延迟的(*os.File).Close调用中的错误。
  • 优雅地处理多个非nil错误,例如来自io.Copy(*os.File).Close的非nil错误。
  • 没有其他答案中存在的不必要的复杂性,例如在同一个文件上调用两次Close,但忽略其中一个调用的错误。
  • 不需要不必要的stat检查文件是否存在或文件类型。这些检查是不必要的:如果对文件类型无效,未来的openread操作将返回错误。其次,这样的检查容易出现竞态条件(例如,在statopen之间的时间内文件可能被删除)。
  • 准确的文档注释。参见:“file”、“regular file”以及当dstpath存在时的行为。文档注释还与包os中其他函数的风格相匹配。
// Copy将srcpath处文件的内容复制到dstpath处的普通文件中。
// 如果dstpath已经存在且不是目录,则函数将截断它。
// 该函数不复制文件模式或文件属性。
func Copy(srcpath, dstpath string) (err error) {
        r, err := os.Open(srcpath)
        if err != nil {
                return err
        }
        defer r.Close() // 忽略错误:文件以只读方式打开。

        w, err := os.Create(dstpath)
        if err != nil {
                return err
        }

        defer func() {
		        // 报告Close的错误(如果有),
		        // 但仅在没有其他错误时这样做。
                if c := w.Close(); c != nil && err == nil {
			            err = c
		        }
	    }()

        _, err = io.Copy(w, r)
        return err
}
英文:
  • Perform the copy in a stream, using io.Copy.
  • Close all opened file descriptors.
  • All errors that should be checked are checked, including the errors in deferred (*os.File).Close calls.
  • Gracefully handle multiple non-nil errors, e.g. non-nil errors from both io.Copy and (*os.File).Close.
  • No unnecessary complications that were present in other answers, such as calling Close twice on the same file but ignoring the error on one of the calls.
  • No unnecessary stat checks for existence or for file type. These checks aren't necessary: the future open and read operations will return an error anyway if it's not a valid operation for the type of file. Secondly, such checks are prone to races (e.g. the file might be removed in the time between stat and open).
  • Accurate doc comment. See: "file", "regular file", and behavior when dstpath exists. The doc comment also matches the style of other functions in package os.
// Copy copies the contents of the file at srcpath to a regular file at dstpath.
// If dstpath already exists and is not a directory, the function truncates it. 
// The function does not copy file modes or file attributes.
func Copy(srcpath, dstpath string) (err error) {
r, err := os.Open(srcpath)
if err != nil {
return err
}
defer r.Close() // ignore error: file was opened read-only.
w, err := os.Create(dstpath)
if err != nil {
return err
}
defer func() {
// Report the error from Close, if any,
// but do so only if there isn&#39;t already
// an outgoing error.
if c := w.Close(); c != nil &amp;&amp; err == nil {
err = c
}
}()
_, err = io.Copy(w, r)
return err
}

答案6

得分: 4

在这种情况下,有几个条件需要验证,我更喜欢非嵌套的代码

func Copy(src, dst string) (int64, error) {
  src_file, err := os.Open(src)
  if err != nil {
    return 0, err
  }
  defer src_file.Close()

  src_file_stat, err := src_file.Stat()
  if err != nil {
    return 0, err
  }

  if !src_file_stat.Mode().IsRegular() {
    return 0, fmt.Errorf("%s 不是一个普通文件", src)
  }

  dst_file, err := os.Create(dst)
  if err != nil {
    return 0, err
  }
  defer dst_file.Close()
  return io.Copy(dst_file, src_file)
}
英文:

In this case there are a couple of conditions to verify, I prefer non-nested code

func Copy(src, dst string) (int64, error) {
src_file, err := os.Open(src)
if err != nil {
return 0, err
}
defer src_file.Close()
src_file_stat, err := src_file.Stat()
if err != nil {
return 0, err
}
if !src_file_stat.Mode().IsRegular() {
return 0, fmt.Errorf(&quot;%s is not a regular file&quot;, src)
}
dst_file, err := os.Create(dst)
if err != nil {
return 0, err
}
defer dst_file.Close()
return io.Copy(dst_file, src_file)
}

答案7

得分: 0

如果你使用的是Windows系统,你可以像这样封装CopyFileW

package utils

import (
	"syscall"
	"unsafe"
)

var (
	modkernel32   = syscall.NewLazyDLL("kernel32.dll")
	procCopyFileW = modkernel32.NewProc("CopyFileW")
)

// CopyFile封装了Windows的CopyFileW函数
func CopyFile(src, dst string, failIfExists bool) error {
	lpExistingFileName, err := syscall.UTF16PtrFromString(src)
	if err != nil {
		return err
	}

	lpNewFileName, err := syscall.UTF16PtrFromString(dst)
	if err != nil {
		return err
	}

	var bFailIfExists uint32
	if failIfExists {
		bFailIfExists = 1
	} else {
		bFailIfExists = 0
	}

	r1, _, err := syscall.Syscall(
		procCopyFileW.Addr(),
		3,
		uintptr(unsafe.Pointer(lpExistingFileName)),
		uintptr(unsafe.Pointer(lpNewFileName)),
		uintptr(bFailIfExists))

	if r1 == 0 {
		return err
	}
	return nil
}

这段代码受到了C:\Go\src\syscall\zsyscall_windows.go中的封装代码的启发。

英文:

If you are on windows, you can wrap CopyFileW like this:

package utils
import (
&quot;syscall&quot;
&quot;unsafe&quot;
)
var (
modkernel32   = syscall.NewLazyDLL(&quot;kernel32.dll&quot;)
procCopyFileW = modkernel32.NewProc(&quot;CopyFileW&quot;)
)
// CopyFile wraps windows function CopyFileW
func CopyFile(src, dst string, failIfExists bool) error {
lpExistingFileName, err := syscall.UTF16PtrFromString(src)
if err != nil {
return err
}
lpNewFileName, err := syscall.UTF16PtrFromString(dst)
if err != nil {
return err
}
var bFailIfExists uint32
if failIfExists {
bFailIfExists = 1
} else {
bFailIfExists = 0
}
r1, _, err := syscall.Syscall(
procCopyFileW.Addr(),
3,
uintptr(unsafe.Pointer(lpExistingFileName)),
uintptr(unsafe.Pointer(lpNewFileName)),
uintptr(bFailIfExists))
if r1 == 0 {
return err
}
return nil
}

Code is inspired by wrappers in C:\Go\src\syscall\zsyscall_windows.go

答案8

得分: -3

这里有一种明显的复制文件的方法:

package main
import (
    "os"
    "log"
    "io"
)

func main() {
    sFile, err := os.Open("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer sFile.Close()

    eFile, err := os.Create("test_copy.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer eFile.Close()

    _, err = io.Copy(eFile, sFile) // 第一个变量显示字节数
    if err != nil {
        log.Fatal(err)
    }

    err = eFile.Sync()
    if err != nil {
        log.Fatal(err)
    }
}

希望对你有帮助!

英文:

Here is an obvious way to copy a file:

package main
import (
&quot;os&quot;
&quot;log&quot;
&quot;io&quot;
)
func main() {
sFile, err := os.Open(&quot;test.txt&quot;)
if err != nil {
log.Fatal(err)
}
defer sFile.Close()
eFile, err := os.Create(&quot;test_copy.txt&quot;)
if err != nil {
log.Fatal(err)
}
defer eFile.Close()
_, err = io.Copy(eFile, sFile) // first var shows number of bytes
if err != nil {
log.Fatal(err)
}
err = eFile.Sync()
if err != nil {
log.Fatal(err)
}
}

答案9

得分: -4

你可以使用"exec"。
对于Windows系统,可以使用exec.Command("cmd","/c","copy","fileToBeCopied destinationDirectory")。
我已经使用过这个命令,它可以正常工作。你可以参考手册了解更多关于exec的详细信息。

英文:

You can use "exec".
exec.Command("cmd","/c","copy","fileToBeCopied destinationDirectory") for windows
I have used this and its working fine. You can refer manual for more details on exec.

huangapple
  • 本文由 发表于 2014年1月11日 18:02:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/21060945.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定