File operations

suggest change

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 501 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

	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: 365
IsDir: false
Mode: 499c40
ModTime: 2020-07-12 22:35:24.037960034 +0000 UTC
OS info: &syscall.Stat_t{Dev:0x9, Ino:0x39e, Nlink:0x1, Mode:0x81a4, Uid:0x0, Gid:0x0, X__pad0:0, Rdev:0x0, Size:365, Blksize:4096, Blocks:8, Atim:syscall.Timespec{Sec:1594593324, Nsec:817460540}, Mtim:syscall.Timespec{Sec:1594593324, Nsec:37960034}, Ctim:syscall.Timespec{Sec:1594593324, Nsec:37960034}, X__unused:[3]int64{0, 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 IsPathExists(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 := IsPathExists(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.

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:



Table Of Contents