A crucial skill for Go programmers is knowing how to write data to both new and existing files. It is something you will use so frequently you will forget that at some point you didn't know how to do it. So, let's not wait any longer — now is a great time to learn how to write data to files, and in particular, how to write strings to text files in Go.
Writing a string to a file
One of the basic file operations is writing a string to a file. In Go, we can easily do this with the help of the WriteFile() function from the os package:
package main
import (
"log"
"os"
)
func main() {
s := "Hello, JB Academy!"
if err := os.WriteFile("test.txt", []byte(s), 0644); err != nil {
log.Fatal(err) // exit the program if we have an unexpected error
}
}
Below you will see the written string within the test.txt file:
Hello, JB Academy!
The WriteFile() function returns an error. It takes the name of a file as its first argument, then a slice of bytes, and finally the permission mode 0644, which lets us create a file and open it in read-and-write mode. After the data is written to the file, WriteFile() automatically closes it.
Take notice that the WriteFile() function creates the file in case it doesn't exist. However, if the file does exist, WriteFile() will truncate its contents before writing any new data to it!
The fmt.Fprint() function
Another way to write strings to a file in Go is through the Fprint() function from the fmt package. The fmt.Fprint() function is very similar to the fmt.Print() function; the difference between them is that instead of writing to the standard output, fmt.Fprint() will write a string to a file.
Now, let's take a look at a basic implementation of the fmt.Fprint() function:
package main
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.Create("hello.txt") // create and open 'hello.txt' in read-and-write mode
if err != nil {
log.Fatal(err)
}
defer file.Close() // close the file before exiting the program
// pass the file descriptor within the 'file' variable and a string to the fmt.Fprint() function
b, err := fmt.Fprint(file, "Hello, JB Academy!")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d bytes written successfully!", b) // 18 bytes written successfully!
}
The Fprint() function takes a file descriptor and a string as its arguments, then it returns the number of bytes written and an error, if any. In very simple terms, a file descriptor is a File type that the program returns from functions such as Create(), Open() and OpenFile() from the os package.
Remember that the os.Create() function creates the file in case it doesn't exist. However, if the file already exists, os.Create() will truncate its contents!
Apart from Fprint(), there are other functions that allow us to write strings to a file within the fmt package: Fprintln() and Fprintf().
Below is the written string within the hello.txt file:
Hello, JB Academy!Writing strings line by line
Another common file-writing operation is writing strings to a file line by line. We can do this by implementing a for...range loop that contains the fmt.Fprintln() function within it:
...
func main() {
file, err := os.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := []string{"Hello, JB Academy!",
"I am writing strings line by line",
"I can write emojis too 🅱️®️🅾️!😤😤",
}
for i, line := range data {
_, err = fmt.Fprintln(file, line) // writes each line of the 'data' slice of strings
if err != nil {
log.Fatal(err)
}
if i == len(data) - 1 {
fmt.Printf("%d lines written successfully!", i + 1) // 3 lines written successfully!
}
}
}
In the above code, we use the fmt.Fprintln() function because it automatically adds a new line \n after each iteration. After the for...range loop finishes iterating over the data slice of strings, we can take a look at the contents of our hello.txt file:
Hello, JB Academy!
I am writing strings line by line
I can write emojis too 🅱️®️🅾️!😤😤
In the code implementation above, the Fprintln() function will always append the new line \n character at the end of the file, thus ending up with 4 lines in our hello.txt file instead of 3.
Writing formatted strings to a file
So far, we've seen how to write a single string and also multiple strings to a file. What if we need to write a formatted string to a file?
We can write formatted strings to a file with the help of the fmt.Fprintf() function. For example, it allows us to use formatting verbs when writing a string to a file:
...
func main() {
file, err := os.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
var pi float64 = 3.14159265359 // create a new float64 variable 'pi'
b, err := fmt.Fprintf(file, "Hello, JB Academy!\nThe value of pi is %.5f", pi)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d bytes written successfully!", b) // 45 bytes written successfully!
}
Just like the other variants of the Fprint() family of functions, Fprintf() takes a file descriptor and a string as its arguments, and then returns the number of bytes written and an error if any.
Now, let's take a look at the contents of hello.txt and check the written pi variable as a string, formatted by the %.5f verb:
Hello, JB Academy!
The value of pi is 3.14159Appending data to a file
As we've seen above, os.Create() creates and opens a file in read-and-write mode. However, if the file already exists, its contents will be truncated! So, if we need to add data to a file and preserve its initial content, we can use the following code:
...
func main() {
// open the file in append-and-write mode - permission mode 0644 is required!
file, err := os.OpenFile("hello.txt", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
additionalLine := "ALWAYS 🕓 make sure 👍 your code 💻 is 💡 clean 🧼 and well 💯 structured 🏛."
b, err := fmt.Fprintln(file, additionalLine) // append the additional line
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d bytes appended successfully!", b) // 92 bytes appended successfully!
}
We can add or append to a file that already has data by opening the file via the os.OpenFile() function, combining the os.O_APPEND and os.O_WRONLY flags with the permission mode 0644. This combination of flags opens the file in append-and-write mode.
If we tried to open the file via the os.Open() function instead, we would not be able to append any data to the file because os.Open() opens the file in read-only mode!
Finally, let's take a look at the final contents of the hello.txt file:
Hello, JB Academy!
The value of pi is 3.14159
ALWAYS 🕓 make sure 👍 your code 💻 is 💡 clean 🧼 and well 💯 structured 🏛.Summary
In this topic, we've learned how to write data to files in Go and, specifically, how to write strings to text files. We've covered in detail the following theory:
-
A basic way to create a text file and write a string to it, via the
os.WriteFile()function; -
How to write multiple string lines to a file, with the
fmt.Fprintln()function and the help of afor...rangeloop; -
How to write formatted strings to a file with the
fmt.Fprintf()function; -
How to open a file that already contains data in append-and-write mode and append string lines to it.
Now, to make sure you remember this theory and properly write data to files in your future Go programs, let's work on a few coding and theory problems.