Go provides file-opening flags represented by constants defined in the os package. These flags determine the behavior of file operations, such as opening, creating, and truncating files. The following is a list of the flags and what they do.
-
os.O_RDONLY: Opens the file as read-only. The file must exist. -
os.O_WRONLY: Opens the file as write-only. If the file exists, its contents are truncated. If it doesn't exist, a new file is created. -
os.O_RDWR: Opens the file for reading and writing. If the file exists, its contents are truncated. If it doesn't exist, a new file is created. -
os.O_APPEND: Appends data to the file when writing. Writes occur at the end of the file. -
os.O_CREATE: Creates a new file if it doesn't exist. -
os.O_EXCL: Used withO_CREATE, it ensures that the file is created exclusively, preventing creation if it already exists. -
os.O_SYNC: Open the file for synchronous I/O operations. Write operations are completed before the call returns. -
os.O_TRUNC: If the file exists and is successfully opened, its contents are truncated to zero length. -
os.O_NONBLOCK: Opens the file in non-blocking mode. Operations like read or write may return immediately with an error if no data is available or the operation would block.
These flags can be combined using the bitwise OR ( |) operator. For example, os.O_WRONLY|os.O_CREATE would open the file for writing, creating it if it doesn't exist.
When using these flags, it's important to check for errors returned by file operations to handle cases where the file cannot be opened or created as expected.
Let's look at how to write text to files in the next section.
The os package also provides a WriteString function that helps you write strings to files. For example, you want to update the log.txt file with a log message:
package main
import (
"log"
"os"
)
func main() {
file, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := "2023-07-11 10:05:12 - Error: Failed to connect to the database. _________________"
_, err = file.WriteString(data)
if err != nil {
log.Fatal(err)
}
}
The code above uses the OpenFile function to open the log.txt file in write-only mode and creates it if it doesn't exist. It then creates a data variable containing a string and uses the WriteString function to write string data to the file.
The code in the previous section deletes the data inside the file before writing the new data every time the code is run, which is acceptable in some cases. However, for a log file, you want it to retain all the previous logs so that the user can refer to them as many times as needed to, for example, perform analytics.
You can open a file in append mode like this:
package main
import (
"log"
"os"
)
func main() {
file, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := "\n 2023-07-11 10:05:12 - Error: Failed to connect to the database.\n __________________ \n"
_, err = file.WriteString(data)
if err != nil {
log.Fatal(err)
}
}
The code above uses the os.O_APPEND to open the file in append mode and will retain all the existing data before adding new data to the log.txt file. You should get an updated file each time you run the code instead of a new file.
Go allows you to write bytes to files as strings with the Write function. For example, if you are streaming data from a server and it is returning bytes, you can write the bytes to a file to be readable:
package main
import (
"log"
"os"
)
func main() {
file, err := os.OpenFile("data.bin", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := []byte{0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x0A}
_, err = file.Write(data)
if err != nil {
log.Fatal(err)
}
}
The code above opens the data.bin file in write-only and append mode and creates it if it doesn't already exist. The code above should return a data.bin file containing the following:
Hello, World!
Next, let's explore how to write formatted data to a file section.
This is one of the most common file-writing tasks when building software applications. For example, if you are building an e-commerce website, you will need to build order confirmation receipts for each buyer, which will contain the details of the user's order. Here is how you can do this in Go:
package main
import (
"fmt"
"log"
"os"
)
func main() {
username, orderNumber := "Adams_adebayo", "ORD6543234"
file, err := os.Create(username + orderNumber + ".pdf")
if err != nil {
log.Fatal(err)
}
defer file.Close()
item1, item2, item3 := "shoe", "bag", "shirt"
price1, price2, price3 := 1000, 2000, 3000
_, err = fmt.Fprintf(file, "Username: %s\nOrder Number: %s\nItem 1: %s\nPrice 1: %d\nItem 2: %s\nPrice 2: %d\nItem 3: %s\nPrice 3: %d\n", username, orderNumber, item1, price1, item2, price2, item3, price3)
if err != nil {
log.Fatal(err)
}
}
The code above defines two variables, username and orderNumber, creates a .pdf based on the variables, checks for errors, and defers the Close function with the defer keyword. It then defines three variables, item1, item2, and item3, formats a message with the fmt's Fprintf all the variables, and writes it to the .pdf file.
The code above then creates an Adams_adebayoORD6543234.pdf file with the following contents:
Username: Adams_adebayo
Order Number: ORD6543234
Item 1: shoe
Price 1: 1000
Item 2: bag
Price 2: 2000
Item 3: shirt
Price 3: 3000
With the help of the encoding/csv package, you can write data to .csv files easily with Go. For example, you want to store new users' profile information in a .csv file after they sign up:
package main
import (
"encoding/csv"
"log"
"os"
)
func main() {
file, err := os.OpenFile("users.csv", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
data := []string{"Adams Adebayo", "30", "Lagos"}
err = writer.Write(data)
if err != nil {
log.Fatal(err)
}
}
The code above opens the users.csv file in write-only and append mode and creates it if it doesn't already exist. It will then use the NewWriter function to create a writer variable, defer the Flush function, create a data variable with the string slice, and write the data to the file with the Write function.
The code above will then return a users.csv file with the following contents:
Adams Adebayo,30,Lagos
Writing JSON data to .json files is a common use case in software development. For example, you are building a small application and want to use a simple .json file to store your application data:
package main
import (
"encoding/json"
"log"
"os"
)
func main() {
file, err := os.OpenFile("users.json", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := map[string]interface{}{
"username": "olodocoder",
"twitter": "@olodocoder",
"email": "[email protected]",
"website": "https://dev.to/olodocoder",
"location": "Lagos, Nigeria",
}
encoder := json.NewEncoder(file)
err = encoder.Encode(data)
if err != nil {
log.Fatal(err)
}
}
The code above opens the users.csv file in write-only and append mode and creates it if it doesn't already exist, defers the Close function, and defines a data variable containing the user data. It then creates an encoder variable with the NewEncoder function and encodes it with the Encoder function.
The code above then returns a users.json file containing the following:
{"email":"[email protected]","location":"Lagos, Nigeria","twitter":"@olodocoder","username":"olodocoder","website":"https://dev.to/olodocoder"}
You can also write XML data to files in Go using the encoding/xml package:
package main
import (
"encoding/xml"
"log"
"os"
)
func main() {
type Person struct {
Name string `xml:"name"`
Age int `xml:"age"`
City string `xml:"city"`
}
file, err := os.OpenFile("users.xml", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := Person{
Name: "John Doe",
Age: 30,
City: "New York",
}
encoder := xml.NewEncoder(file)
err = encoder.Encode(data)
if err != nil {
log.Fatal(err)
}
}
The code above defines a Person struct with three fields, opens the users.xml file in write-only and append mode and creates it if it doesn't already exist, defers the Close function, and defines a data variable that contains the user data. It then creates an encoder variable with the NewEncoder function and encodes it with the Encoder function.
The code above should return a user.xml file that contains the following contents:
<Person><name>John Doe</name><age>30</age><city>New York</city></Person>
Go enables you to rename files from your code using the Rename function:
package main
import (
"fmt"
"os"
)
func main() {
err := os.Rename("users.xml", "data.xml")
if err != nil {
fmt.Println(err)
}
}
The code above renames the users.xml file created in the previous section to data.xml.
Go enables you to delete files with the Remove function:
package main
import (
"fmt"
"os"
)
func main() {
err := os.Remove("data.bin")
if err != nil {
fmt.Println(err)
}
fmt.Println("File deleted")
}
The code above deletes the data.bin file from the specified path.
Now that you understand how to write and manipulate different types of files in Go, let's explore how to work with directories.
In addition to files, Go also provides functions that you can use to perform different tasks in applications. We will explore some of these tasks in the following sections.
Go provides a Mkdir function that you can use to create an empty directory:
package main
import (
"fmt"
"os"
)
func main() {
err := os.Mkdir("users", 0755)
if err != nil {
fmt.Println(err)
}
fmt.Println("Directory Created Successfully")
}
The code above creates a users folder in the current working directory.
You can create multiple directories in Go using the MkdirAll function:
package main
import (
"fmt"
"os"
)
func main() {
err := os.MkdirAll("data/json_data", 0755)
if err != nil {
fmt.Println(err)
}
fmt.Println("Directory Created Successfully")
}
The code above will create a data directory and a json_data directory inside it.
Note: If a
datadirectory already exists, the code will only add ajson_datadirectory inside it.
To avoid errors, checking if a directory exists before creating a file or directory inside is good practice. You can use the Stat function and the IsNotExist function to do a quick check:
package main
import (
"fmt"
"os"
)
func main() {
if _, err := os.Stat("data/csv_data"); os.IsNotExist(err) {
fmt.Println("Directory does not exist")
} else {
fmt.Println("Directory exists")
}
}
The code above returns a message based on the results of the check. In my case, it will return the following:
Directory exists
You can also use the Rename function to rename directories:
package main
import (
"fmt"
"os"
)
func main() {
err := os.Rename("data/csv_data", "data/xml_data")
if err != nil {
fmt.Println(err)
}
}
The code above renames the data/csv_data directory to data/xml_data.
You can use the Remove function to delete folders in your applications:
package main
import (
"fmt"
"os"
)
func main() {
err := os.Remove("data/json_data")
if err != nil {
fmt.Println(err)
}
}
The code above removes the json_data directory from the data directory.
Go provides a RemoveAll function that allows you to remove all the directories and everything inside them, including files and folders:
package main
import (
"fmt"
"os"
)
func main() {
err := os.RemoveAll("users")
if err != nil {
fmt.Println(err)
}
fmt.Println("users directory and all it's content has been removed")
}
The code above deletes the users directory and everything inside it.
Note: It's good practice to check if the directory exists before attempting to delete it.
You can retrieve a list of all the files and directories in a directory using the ReadDir function:
package main
import (
"fmt"
"os"
)
func main() {
dirEntries, err := os.ReadDir("data")
if err != nil {
fmt.Println(err)
}
for _, entry := range dirEntries {
fmt.Println(entry.Name())
}
}
The code above returns a list of all the directories and files inside the data folder.
Now that you know how to work with directories in Go applications, let's explore some of the advanced file operations in the next section.
In this section, we will explore some of the advanced file operations you might encounter in Go applications.
Working with compressed files is uncommon, but here's how to create a .txt file inside a compressed file using the compress/gzip package:
package main
import (
"compress/gzip"
"fmt"
"log"
"os"
)
func main() {
file, err := os.OpenFile("data.txt.gz", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer file.Close()
gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()
data := "Data to compress"
_, err = gzipWriter.Write([]byte(data))
if err != nil {
log.Fatal(err)
}
fmt.Println("File compressed successfully")
}
The code above creates a data.txt.gz, which contains a data.txt file in the working directory.
When building applications that require secure files, you can create an encrypted file with Go's crypto/aes and crypto/cipher packages:
package main
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"log"
"os"
)
func main() {
// file, err := os.OpenFile("encrypted.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
file, err := os.Create("encrypted.txt")
if err != nil {
log.Fatal(err)
fmt.Println("Error")
}
defer file.Close()
key := []byte("cacf2ebb8cf3402964356547f20cced5")
plaintext := []byte("This is a secret! Don't tell anyone!🤫")
block, err := aes.NewCipher(key)
if err != nil {
log.Fatal(err)
fmt.Println("Error")
}
ciphertext := make([]byte, len(plaintext))
stream := cipher.NewCTR(block, make([]byte, aes.BlockSize))
stream.XORKeyStream(ciphertext, plaintext)
_, err = file.Write(ciphertext)
if err != nil {
log.Fatal(err)
fmt.Println("Error")
}
fmt.Println("Encrypted file created successfully")
}
The code above creates an encrypted.txt file containing an encrypted version of the plaintext string:
?Э_g?L_.?^_?,_?_;?S???{?Lؚ?W4r
W?8~?
Copying existing files to different locations is something we all do frequently. Here's how to do it in Go:
package main
import (
"fmt"
"io"
"os"
)
func main() {
srcFile, err := os.Open("data/json.go")
if err != nil {
fmt.Println(err)
}
defer srcFile.Close()
destFile, err := os.Create("./json.go")
if err != nil {
fmt.Println(err)
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
fmt.Println(err)
}
fmt.Println("Copy done!")
}
The code above copies the json.go file in the data directory and its contents and then creates another json.go with the same in the root directory.
Go allows you to get the properties of a file with the Stat function:
package main
import (
"fmt"
"os"
)
func main() {
fileInfo, err := os.Stat("config.json")
if err != nil {
fmt.Println(err)
}
fmt.Println("File name:", fileInfo.Name())
fmt.Println("Size in bytes:", fileInfo.Size())
fmt.Println("Permissions:", fileInfo.Mode())
fmt.Println("Last modified:", fileInfo.ModTime())
fmt.Println("File properties retrieved successfully")
}
The code above returns the name, size, permissions, and last modified date of the config.json file:
File name: config.json
Size in bytes: 237
Permissions: -rw-r--r--
Last modified: 2023-07-11 22:46:59.705875417 +0100 WAT
File properties retrieved successfully
You can get the current working directory of your application in Go:
package main
import (
"fmt"
"os"
)
func main() {
wd, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
fmt.Println("Current working directory:", wd)
}
The code above will return the full path of my current working directory:
Current working directory: /Users/user12/Documents/gos/go-files
This tutorial i found on the web and written by By Adebayo Adams, thanks for this beautiful doc! I just convert to markdown for a better read!