File operations
suggest changeGet 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:
- it treats files and directories the same. If a path exists and you want to distinguish between directory and a file, you would need to call
IsDir()
on result ofos.Lstat
- if a file is a symbolic link, do we test the link or the real file it links to? We used
os.Lstat
so we test the link. We could also useos.Stat
to resolve the symbolic link - “path doesn’t exist” is only one of possible errors returned by
os.Lstat
. Do we want to distinguish between “file doesn’t exist” and “file exists and we don’t have access”? We decided to be more informative but in some cases it would be simpler to just return a bool and always return false ifos.Lstat
fails
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:
- should we over-write existing files or return error if destination exists? We decided to over-write
- what permissions should new file have? We decided for the simplest case of using default permissions. Another option would be to copy permissions from source or allow the caller to provide permissions
If you want different behavior, you will have to modify the code as needed.