9. Input/Output¶
for out of the abundance of the heart the mouth speaketh. – Bible
Users interact with software systems through various input/output mechanisms. Some of the commonly used mechanisms are these:
web browser for web applications using various controllers/widgets
mobile for mobile applications
shell for command line applications
desktop application with native controllers/widgets
To provide these kind of user interactions, you will be required to use specialized libraries. If the standard library doesn’t provide what you are looking for, you may need to use third party libraries. This chapter cover basic mechanisms provided at the language level which you can use for input/output.
We have already seen some of the basic input/output techniques in the last few chapters. This chapter will go though more input/output mechanisms available in Go.
9.1. Command Line Arguments¶
The command line arguments as user input and console for output is the
way command line programs are designed. Sometimes output will be other
files and devices. You can access all the command line arguments using
the Args
array/slice attribute available in os
package. Here is
an example:
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8func main() {
9 fmt.Println(os.Args)
10 fmt.Println(os.Args[0])
11 fmt.Println(os.Args[1])
12 fmt.Println(os.Args[2])
13}
You can run this program with minimum two additional arguments:
$ go build cmdline.go
$ ./cmdline one -two
[cmdline one -two]
./cmdline
one
-two
As you can see, it’s difficult to parse command line arguments like this. Go standard library has a package named flag which helps to easily parse command line arguments. This chapter has a section to explain the flag package.
9.2. Files¶
Reading and writing data to files is a common I/O operation in computer programming. You can manipulate files using the os and io packages. It works with both text files and binary files. For the simplicity of this section, all the examples given here works with text files.
Consider there exists a file named poem.txt
with this text:
I wandered lonely as a cloud
That floats on high o'er vales and hills,
When all at once I saw a crowd,
A host, of golden daffodils;
Beside the lake, beneath the trees,
Fluttering and dancing in the breeze.
Here is a program to read the the whole file and print:
1package main
2
3import (
4 "fmt"
5 "io"
6 "os"
7)
8
9func main() {
10 fd, err := os.Open("poem.txt")
11 if err != nil {
12 fmt.Println("Error reading file:", err)
13 }
14 for {
15 chars := make([]byte, 50)
16 n, err := fd.Read(chars)
17 if n == 0 && err == io.EOF {
18 break
19 }
20 fmt.Print(string(chars))
21 }
22 fd.Close()
23}
When you run this program, you will get the whole text as output. In the
line number 10, the Open
function from the os
package is called
to open the file for reading. It returns a file descriptor [1] and
error. In the line number 14, an infinite loop is stared to read the
content. Line 15 initialize a slice of bytes of length 50. The
fd.Read
method reads the given length of characters and writes to
the given slice. It returns the number of characters read and error. The
io.EOF
error is returned when end of file is reached. This is used
as the condition to break the loop.
Here is a program to write some text to a file:
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8func main() {
9 fd, err := os.Create("hello.txt")
10 if err != nil {
11 fmt.Println("Cannot write file:", err)
12 os.Exit(1)
13 }
14 fd.Write([]byte("Hello, World!\n"))
15 fd.Close()
16}
In th line number 9, the Create function from the os package is
called open the file for writing. It returns a file descriptor and
error. In the line number 14, the Write method is give a slice of
bytes to write. After running the program you can see the text in the
hello.txt
file.
$ go run writefile.go
$ cat hello.txt
Hello, World!
9.3. Standard Streams¶
Standard streams [2] are input and output communication channels between a computer program and its environment. The three input/output connections are called standard input (stdin), standard output (stdout) and standard error (stderr).
Stdin, Stdout, and Stderr are open files pointing to the standard input, standard output, and standard error file descriptors.
The fmt package has functions to read values interactively.
Here is an example:
1package main
2
3import "fmt"
4
5func main() {
6 var name string
7 fmt.Printf("Enter your name: ")
8 fmt.Scanf("%s", &name)
9 fmt.Println("Your name:", name)
10}
The Scanf function reads the standard input. The first argument is the format and the second one is the pointer variable. The value read from standard input cab be accessed using the given variable.
You can run the above program in different ways:
$ go run code/io/readprint.go
Enter your name: Baiju
Your name: Baiju
$ echo "Baiju" |go run code/io/readprint.go
Enter your name: Your name: Baiju
$ go run code/io/readprint.go << EOF
> Baiju
> EOF
Enter your name: Your name: Baiju
$ echo "Baiju" > /tmp/baiju.txt
$ go run code/io/readprint.go < /tmp/baiju.txt
Enter your name: Your name: Baiju
As you can see from this program, the Printf function writes to standard output and the Scanf reads the standard input. Go can also writes to standard error output stream.
The io package provides a set of interfaces and functions that allow developers to work with different types of input and output streams.
Consider a use case to convert everything that comes to standard input
to convert to upper case. This can be achieved by reading all standard
input using io.ReadAll
and converting to upper case. Here is code:
1package main
2
3import (
4 "fmt"
5 "io"
6 "os"
7 "strings"
8)
9
10func main() {
11 stdin, err := io.ReadAll(os.Stdin)
12 if err != nil {
13 panic(err)
14 }
15 str := string(stdin)
16 newStr := strings.TrimSuffix(str, "\n")
17 upper := strings.ToUpper(newStr)
18 fmt.Println(upper)
19}
You can run this program similar to how you did with the previous program.
You can use fmt.Fprintf with os.Stderr as the first argument to write to standard error.
fmt.Fprintf(os.Stderr, "This goes to standard error: %s", "OK")
Alternatively, you can call WriteString method of os.Stderr:
os.Stderr.WriteString("This goes to standard error")
9.4. Using flag Package¶
As you have noticed before os.Args
attribute in the os package
provides access to all command line arguments. The flag package
provides an easy way to parse command line arguments.
You can define string, boolean, and integer flags among others using the flag package..
Here is an integer flag declaration:
var pageCount = flag.Int("count", 240, "number of pages")
The above code snippet defines an integer flag with name as count
and it is stored in a variable with the name as pageCount
. The type
of the variable is *int. Similar to this integer flag, you can
defines flags of other types.
Once all the flags are defined, you can parse it like this:
flag.Parse()
The above Parse
function call parse the command line arguments and
store the values in the given variables.
Once the flags are parsed, you can dereference it like this:
fmt.Println("pageCount: ", *pageCount)
To access non-flag arguments:
flag.Args()
The above call returns a the arguments as a slice of strings. It contains arguments not parsed as flags.
Cobra is a third party package providing a simple interface to create command line interfaces. Cobra also helps to generate applications and command files. Many of the most widely used Go projects are built using Cobra. This is the Cobra website: https://github.com/spf13/cobra
9.5. String Formatting¶
Go supports many string format options. To get the default format of any
value, you can use %v
as the format string. Here is an example which
print formatted values using %v
:
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 fmt.Printf("Value: %v, Type: %T\n", "Baiju", "Baiju")
9 fmt.Printf("Value: %v, Type: %T\n", 7, 7)
10 fmt.Printf("Value: %v, Type: %T\n", uint(7), uint(7))
11 fmt.Printf("Value: %v, Type: %T\n", int8(7), int8(7))
12 fmt.Printf("Value: %v, Type: %T\n", true, true)
13 fmt.Printf("Value: %v, Type: %T\n", 7.0, 7.0)
14 fmt.Printf("Value: %v, Type: %T\n", (1 + 6i), (1 + 6i))
15}
The %T
shows the type of the value. The output of the above program
will be like this.
Value: Baiju, Type: string
Value: 7, Type: int
Value: 7, Type: uint
Value: 7, Type: int8
Value: true, Type: bool
Value: 7, Type: float64
Value: (1+6i), Type: complex128
If you use a %+v
as the format string for struct it shows the field
names. See this example:
1package main
2
3import (
4 "fmt"
5)
6
7// Circle represents a circle
8type Circle struct {
9 radius float64
10 color string
11}
12
13func main() {
14 c := Circle{radius: 76.45, color: "blue"}
15 fmt.Printf("Value: %#v\n", c)
16 fmt.Printf("Value with fields: %+v\n", c)
17 fmt.Printf("Type: %T\n", c)
18}
If you run the above program, the output is going to be like this:
Value: {76.45 blue}
Value with fields: {radius:76.45 color:blue}
Type: main.Circle
As you can see from the output, in the first line %v
doesn’t show
the fields. But in the second line, %+v
shows the struct fields.
The %#v
shows the representation of the value. Here is a modified
version of above program to print few values of primitive type.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 fmt.Printf("Value: %#v, Type: %T\n", "Baiju", "Baiju")
9 fmt.Printf("Value: %#v, Type: %T\n", 7, 7)
10 fmt.Printf("Value: %#v, Type: %T\n", uint(7), uint(7))
11 fmt.Printf("Value: %#v, Type: %T\n", int8(7), int8(7))
12 fmt.Printf("Value: %#v, Type: %T\n", true, true)
13 fmt.Printf("Value: %#v, Type: %T\n", 7.0, 7.0)
14 fmt.Printf("Value: %#v, Type: %T\n", (1 + 6i), (1 + 6i))
15}
Value: "Baiju", Type: string
Value: 7, Type: int
Value: 0x7, Type: uint
Value: 7, Type: int8
Value: true, Type: bool
Value: 7, Type: float64
Value: (1+6i), Type: complex128
As you can see in the representation, strings are written within quotes. You can also see representation of few other primitive types.
If you want a literal %
sign, use two %
signs next to each
other. Here is a code snippet:
fmt.Println("Tom scored 92%% marks")
The default string representation of custom types can be changed by
implementing fmt.Stringer
interafce. The interface definition is
like this:
type Stringer interface {
String() string
}
As per the Stringer
interface, you need to create a String
function which return a string. Now the value printed will be whatever
returned by that function. Here is an example:
1package main
2
3import (
4 "fmt"
5 "strconv"
6)
7
8// Temperature repesent air temperature
9type Temperature struct {
10 Value float64
11 Unit string
12}
13
14func (t Temperature) String() string {
15 f := strconv.FormatFloat(t.Value, 'f', 2, 64)
16 return f + " degree " + t.Unit
17}
18
19func main() {
20 temp := Temperature{30.456, "Celsius"}
21 fmt.Println(temp)
22 fmt.Printf("%v\n", temp)
23 fmt.Printf("%+v\n", temp)
24 fmt.Printf("%#v\n", temp)
25}
The output of the above program will be like this:
30.46 degree Celsius
30.46 degree Celsius
30.46 degree Celsius
main.Temperature{Value:30.456, Unit:"Celsius"}
9.6. Exercises¶
Exercise 1: Write a program to read length and width of a rectangle
through command line arguments and print the area. Use -length
switch to get length and -width
switch to get width. Represent the
rectangle using a struct.
Solution:
1package main
2
3import (
4 "flag"
5 "fmt"
6 "log"
7 "os"
8)
9
10// Rectangle represents a rectangle shape
11type Rectangle struct {
12 Length float64
13 Width float64
14}
15
16// Area return the area of a rectangle
17func (r Rectangle) Area() float64 {
18 return r.Length * r.Width
19}
20
21var length = flag.Float64("length", 0, "length of rectangle")
22var width = flag.Float64("width", 0, "width of rectangle")
23
24func main() {
25 flag.Parse()
26 if *length <= 0 {
27 log.Println("Invalid length")
28 os.Exit(1)
29 }
30 if *width <= 0 {
31 log.Println("Invalid width")
32 os.Exit(1)
33 }
34 r := Rectangle{Length: *length, Width: *width}
35 a := r.Area()
36 fmt.Println("Area: ", a)
37}
You can run the program like this:
$ go run rectangle.go -length 2.5 -width 3.4
Area: 8.5
9.6.1. Additional Exercises¶
Answers to these additional exercises are given in the Appendix A.
Problem 1: Write a program to format a complex number as used in
mathematics. Example: 2 + 5i
Use a struct like this to define the complex number:
type Complex struct {
Real float64
Imaginary float64
}
9.7. Summary¶
This chapter discussed the input/output (I/O) features of the Go programming language. It explained how to use command line arguments and interactive input, and how to use the flag package to parse command line arguments. It also explained various string formatting techniques.