Working with directories and files can be difficult when you switch between platforms (Windows and Linux, for example). Go provides cross-platform support to work with files and directories in the os and ioutils packages. We've already seen examples of ioutils, but now we'll explore how to use them in another way!
Working with directories and files
Getting ready
Refer to the Getting ready section's steps in the Using the common I/O interfaces recipe.
How to do it...
These steps cover writing and running your application:
- From your terminal/console application, create a new directory called chapter1/filedirs.
- Navigate to this directory.
- Copy tests from https://github.com/agtorre/go-cookbook/tree/master/chapter1/filedirs, or use this as an exercise to write some of your own code!
- Create a file called dirs.go with the following contents:
package filedirs
import (
"errors"
"io"
"os"
)
// Operate manipulates files and directories
func Operate() error {
// this 0777 is similar to what you'd see with chown
// on a command line this will create a director
// /tmp/example, you may also use an absolute path
// instead of a relative one
if err := os.Mkdir("example_dir", os.FileMode(0755));
err != nil {
return err
}
// go to the /tmp directory
if err := os.Chdir("example_dir"); err != nil {
return err
}
// f is a generic file object
// it also implements multiple interfaces
// and can be used as a reader or writer
// if the correct bits are set when opening
f, err := os.Create("test.txt")
if err != nil {
return err
}
// we write a known-length value to the file and
// validate that it wrote correctly
value := []byte("hellon")
count, err := f.Write(value)
if err != nil {
return err
}
if count != len(value) {
return errors.New("incorrect length returned
from write")
}
if err := f.Close(); err != nil {
return err
}
// read the file
f, err = os.Open("test.txt")
if err != nil {
return err
}
io.Copy(os.Stdout, f)
if err := f.Close(); err != nil {
return err
}
// go to the /tmp directory
if err := os.Chdir(".."); err != nil {
return err
}
// cleanup, os.RemoveAll can be dangerous if you
// point at the wrong directory, use user input,
// and especially if you run as root
if err := os.RemoveAll("example_dir"); err != nil {
return err
}
return nil
}
- Create a file called bytes.go with the following contents:
package filedirs
import (
"bytes"
"io"
"os"
"strings"
)
// Capitalizer opens a file, reads the contents,
// then writes those contents to a second file
func Capitalizer(f1 *os.File, f2 *os.File) error {
if _, err := f1.Seek(0, 0); err != nil {
return err
}
var tmp = new(bytes.Buffer)
if _, err := io.Copy(tmp, f1); err != nil {
return err
}
s := strings.ToUpper(tmp.String())
if _, err := io.Copy(f2, strings.NewReader(s)); err !=
nil {
return err
}
return nil
}
// CapitalizerExample creates two files, writes to one
//then calls Capitalizer() on both
func CapitalizerExample() error {
f1, err := os.Create("file1.txt")
if err != nil {
return err
}
if _, err := f1.Write([]byte(`this file contains a
number of words and new lines`)); err != nil {
return err
}
f2, err := os.Create("file2.txt")
if err != nil {
return err
}
if err := Capitalizer(f1, f2); err != nil {
return err
}
if err := os.Remove("file1.txt"); err != nil {
return err
}
if err := os.Remove("file2.txt"); err != nil {
return err
}
return nil
}
- Create a new directory named example.
- Navigate to example.
- Create a main.go file with the following contents and ensure that you modify the filedirs package import to use the path you set up in step 2:
package main
import "github.com/agtorre/go-cookbook/chapter1/filedirs"
func main() {
if err := filedirs.Operate(); err != nil {
panic(err)
}
if err := filedirs.CapitalizerExample(); err != nil {
panic(err)
}
}
- Run go run main.go.
- You may also run these:
go build
./example
You should see the following output:
$ go run main.go
hello
- If you copied or wrote your own tests, go up one directory and run go test, and ensure all tests pass.
How it works...
If you're familiar with files in Unix, the Go os library should feel very familiar. You can do basically all common operations--stat a file to collect attributes, collect a file with different permissions, and create and modify directories and files. We performed a number of manipulations to directories and files and then cleaned up after ourselves.
Working with file objects is very similar to in-memory streams. Files also provide a number of convenience functions directly, such as Chown, Stat, and Truncate. The easiest way to get comfortable with files is to make use of them. In all the previous recipes, we have to be careful to clean up after our programs.
Working with files is a very common operation when building backend applications. Files can be used for configuration, secret keys, as temporary storage, and more. Go wraps OS system calls using the os package and allows the same functions to operate regardless of whether you're using Windows or Unix.
Once your file is opened and stored in a File struct, it can easily be passed into a number of interfaces discussed earlier. All the earlier examples of working with buffers and in-memory data streams can be replaced directly with file objects. This may be useful for things such as writing all logs to stderr and the file at the same time with a single write call.