Essential Go Working with files and I/O  Suggest an edit

File operations

Get file size

// GetFileSize returns file size or error if e.g. file doesn't exist
func GetFileSize(path string) (int64, error) {
	st, err := os.Lstat(path)
	if err != nil {
		return -1, err
	}
	return st.Size(), nil
}

func main() {
	path := "main.go"
	size, err := GetFileSize(path)
	if err != nil {
		log.Fatalf("GetFileSize failed with '%s'\n", err)
	}
	fmt.Printf("File %s is %d bytes in size\n", path, size)
}
File main.go is 494 bytes in size

Instead of os.Stat we can also use os.Lstat. The difference is that os.Stat follows symbolic links and os.Lstat doesn’t.

In other words: for a symbolic link os.Lstat returns information about link and os.Stat about the file that it links to.

Get information about the file

func main() {
	st, err := os.Stat("main.go")
	if err != nil {
		log.Fatalf("GetFileSize failed with '%s'\n", err)
	}
	fmt.Printf(`Name: %s
Size: %d
IsDir: %v
Mode: %x
ModTime: %s
OS info: %#v
`, st.Name(), st.Size(), st.IsDir(), st.Mode, st.ModTime(), st.Sys())
}
Name: main.go
Size: 356
IsDir: false
Mode: 10944d0
ModTime: 2018-10-18 02:25:02.62046003 -0700 PDT
OS info: &syscall.Stat_t{Dev:16777220, Mode:0x81a4, Nlink:0x1, Ino:0x2016a3e13, Uid:0x1f5, Gid:0x14, Rdev:0, Pad_cgo_0:[4]uint8{0x0, 0x0, 0x0, 0x0}, Atimespec:syscall.Timespec{Sec:1539854702, Nsec:882319277}, Mtimespec:syscall.Timespec{Sec:1539854702, Nsec:620460030}, Ctimespec:syscall.Timespec{Sec:1539854702, Nsec:620460030}, Birthtimespec:syscall.Timespec{Sec:1539854702, Nsec:620387985}, Size:356, Blocks:8, Blksize:4096, Flags:0x0, Gen:0x0, Lspare:0, Qspare:[2]int64{0, 0}}

Check if a file exists

// IsPathxists returns true if a given path exists, false if it doesn't.
// It might return an error if e.g. file exists but you don't have
// access
func IsPathxists(path string) (bool, error) {
	_, err := os.Lstat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	// error other than not existing e.g. permission denied
	return false, err
}

func printExists(path string) {
	exists, err := IsPathxists(path)
	if err == nil {
		fmt.Printf("File '%s' exists: %v\n", path, exists)
	} else {
		fmt.Printf("IsFileExists('%s') failed with '%s'\n", path, err)
	}
}
func main() {
	printExists("main.go")
	printExists("non-existent-file.txt")
}
File 'main.go' exists: true
File 'non-existent-file.txt' exists: false

Checking if a file exists is surprisingly tricky and it’s impossible to write a generic function that handles all the nuances.

Here are decisions we made:

Delete a file

path := "foo.txt"
err := os.Remove(path)
if err != nil {
    if os.IsNotExist(err) {
        fmt.Printf("os.Remove failed because file doesn't exist\n")
    } else {
        fmt.Printf("os.Remove failed with '%s'\n", err)
    }
}

os.Remove returns an error for files that don’t exist.

Usually you want to ignore such errors which you can do by testing error with os.IsNotExist(err).

Rename a file

oldPath := "old_name.txt"
newPath := "new_name.txt"
err := os.Rename(oldPath, newPath)
if err != nil {
    fmt.Printf("os.Rename failed with '%s'\n", err)
}

Copy a file

// CopyFile copies a src file to dst
func CopyFile(dst, src string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	_, err = io.Copy(dstFile, srcFile)
	err2 := dstFile.Close()
	if err == nil && err2 != nil {
		err = err2
	}
	if err != nil {
		// delete the destination if copy failed
		os.Remove(dst)
	}
	return err
}

Writing a generic function for copying files is tricky and it’s impossible to write a function that serves all use cases.

Here are policy decisions we made:

If you want different behavior, you will have to modify the code as needed.

  ↑ ↓ to navigate     ↵ to select     Esc to close