Go Fundamentals - Sample

Table of Contents

Chapter 14.1: Directory Entries and File Information

Before we can begin using files for our programs, we need to, first, understand the basics of how files work in Go.

Consider the following file tree. We will be using this file tree to aid in our understanding of files in Go. This tree contains .txt files, as well as, special directories, testdata, _ignore, and .hidden. We will cover the importance of these files, and directories, as we progress.

$ tree -a

.
├── .hidden
│   └── d.txt
├── a.txt
├── b.txt
├── e
│   ├── f
│   │   ├── _ignore
│   │   │   └── i.txt
│   │   ├── g.txt
│   │   └── h.txt
│   └── j.txt
└── testdata
    └── c.txt

5 directories, 8 files

Listing 14.1: File tree containing special folders; .hidden, _ignore, and testdata.

Reading a Directory

In order to know which files we can work with, we need to know which files are in a directory. To do this we can use the os.ReadDir function, Listing 14.2.

$ go doc os.ReadDir

package os // import "os"

func ReadDir(name string) ([]DirEntry, error)
    ReadDir reads the named directory, returning all its directory entries
    sorted by filename. If an error occurs reading the directory, ReadDir
    returns the entries it was able to read before the error, along with the
    error.

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.2: The os.ReadDir function.

The os.ReadDir function takes a path as a parameter, and returns a slice of os.DirEntry values, Listing 14.3. Each os.DirEntry value represents a file or directory in the directory, and contains information about the file or directory.

$ go doc os.DirEntry

package os // import "os"

type DirEntry = fs.DirEntry
    A DirEntry is an entry read from a directory (using the ReadDir function or
    a File.ReadDir method).

func ReadDir(name string) ([]DirEntry, error)

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.3: The os.DirEntry type.

In go1.16 the fs package was added, which provides interface around the file system. The result of this was that many types were aliased to the fs package. This means that you may need to hunt a little further to find the deeper documentation.

For example, os.DirEntry was aliased to fs.DirEntry in go1.16, Listing 14.4.

$ go doc fs.DirEntry

package fs // import "io/fs"

type DirEntry interface {
	// Name returns the name of the file (or subdirectory) described by the entry.
	// This name is only the final element of the path (the base name), not the entire path.
	// For example, Name would return "hello.go" not "home/gopher/hello.go".
	Name() string

	// IsDir reports whether the entry describes a directory.
	IsDir() bool

	// Type returns the type bits for the entry.
	// The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
	Type() FileMode

	// Info returns the FileInfo for the file or subdirectory described by the entry.
	// The returned FileInfo may be from the time of the original directory read
	// or from the time of the call to Info. If the file has been removed or renamed
	// since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
	// If the entry denotes a symbolic link, Info reports the information about the link itself,
	// not the link's target.
	Info() (FileInfo, error)
}
    A DirEntry is an entry read from a directory (using the ReadDir function or
    a ReadDirFile's ReadDir method).

func FileInfoToDirEntry(info FileInfo) DirEntry
func ReadDir(fsys FS, name string) ([]DirEntry, error)

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.4: The fs.DirEntry type.

We can use the os.ReadDir function to read the contents of the data directory. We then print the names to the console. If the file is a directory, we prepend it with a ->.

func main() {
	files, err := os.ReadDir("data")
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		if file.IsDir() {
			fmt.Println("->", file.Name())
			continue
		}
		fmt.Println(file.Name())
	}
}
Listing 14.5: Using os.ReadDir to read the contents of a directory.

As we can see from the output of the program, only the files in the data directory are listed. The os.ReadDir function will only read the contents of the directory, and not the contents of any subdirectories. To get a full list of files, including the contents of subdirectories, we will need to walk the directories ourselves. We will discuss this more later.

$ go run .

-> .hidden
a.txt
b.txt
-> e
-> testdata

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.6: Output of Listing 14.5.

The FileInfo Interface

The main source for metadata about files is the fs.FileInfo interface, Listing 14.7. In go1.16 the os.FileInfo type was aliased to fs.FileInfo. From this interface we can get the name of the file, the size of the file, the time the file was last modified, and the mode, or permissions, of the file. We can also tell if the file is a directory, or a regular file.

$ go doc fs.FileInfo

package fs // import "io/fs"

type FileInfo interface {
	Name() string       // base name of the file
	Size() int64        // length in bytes for regular files; system-dependent for others
	Mode() FileMode     // file mode bits
	ModTime() time.Time // modification time
	IsDir() bool        // abbreviation for Mode().IsDir()
	Sys() any           // underlying data source (can return nil)
}
    A FileInfo describes a file and is returned by Stat.

func Stat(fsys FS, name string) (FileInfo, error)

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.7: The fs.FileInfo interface.

Consider Listing 14.8. We read the contents of the data directory, and print the mode, size, and name of each file.

func main() {
	files, err := os.ReadDir("data")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Mode\t\tSize\tName")
	for _, file := range files {
		info, err := file.Info()
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("%s\t%d\t\t%s\n", info.Mode(), info.Size(), info.Name())
	}
}

$ go run .

Mode		Size	Name
drwxr-xr-x	96		.hidden
-rw-r--r--	31		a.txt
-rw-r--r--	9		b.txt
drwxr-xr-x	128		e
drwxr-xr-x	96		testdata

--------------------------------------------------------------------------------
Go Version: go1.23.0
Listing 14.8: Using fs.FileInfo to print information about files.

Stating a File

The os.ReadDir function returns a slice of os.DirEntry values, from which we can get the fs.FileInfo from, os.DirEntry.Info. To get the fs.FileInfo for a single file or directory we can use the os.Stat function, Listing 14.9.

$ go doc os.Stat

package os // import "os"

func Stat(name string) (FileInfo, error)
    Stat returns a FileInfo describing the named file. If there is an error,
    it will be of type *PathError.

--------------------------------------------------------------------------------
Go Version: go1.23.0

Listing 14.9: The os.Stat function.

Consider Listing 14.10, which prints the mode, size, and name of the data/a.txt file.

func main() {
	info, err := os.Stat("data/a.txt")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\t%d\t%s\n", info.Mode(), info.Size(), info.Name())
}

$ go run .

-rw-r--r--	31	a.txt

--------------------------------------------------------------------------------
Go Version: go1.23.0
Listing 14.10: Using os.Stat to get information about a file.