Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Go Standard Library Cookbook
Go Standard Library Cookbook

Go Standard Library Cookbook: Over 120 specific ways to make full use of the standard library components in Golang

eBook
AU$41.99 AU$60.99
Paperback
AU$75.99
Subscription
Free Trial
Renews at AU$24.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Go Standard Library Cookbook

Interacting with the Environment

In this chapter, the following recipes are covered:

  • Retrieving the Golang version
  • Accessing program arguments
  • Creating a program interface with the flag package
  • Getting and setting environment variables with default values
  • Retrieving the current working directory
  • Getting the current process PID
  • Handling operating system signals
  • Calling an external process
  • Retrieving child process information
  • Reading/writing from the child process
  • Shutting down the application gracefully
  • File configuration with functional options

Introduction

Every program, once it is executed, exists in the environment of the operating system. The program receives input and provides output to this environment. The operating system also needs to communicate with the program to let it know what's happening outside. And finally, the program needs to respond with appropriate actions.

This chapter will walk you through the basics of the discovery of the system environment, the program parameterization via program arguments, and the concept of the operating system signals. You will also learn how to execute and communicate with the child process.

Retrieving the Golang version

While building a program, it is a good practice to log the environment settings, build version, and runtime version, especially if your application is more complex. This helps you to analyze the problem, in case something goes wrong.

Besides the build version and, for example, the environmental variables, the Go version by which the binary was compiled could be included in the log. The following recipe will show you how to include the Go runtime version into such program information.

Getting ready

Install and verify the Go installation. The following steps could help:

  1. Download and install Go on your machine.
  2. Verify that your GOPATH and GOROOT environmental variables are set properly.
  3. Open your Terminal and execute go version. If you get output with a version name, then Go is installed properly.
  4. Create a repository in the GOPATH/src folder.

How to do it...

The following steps cover the solution:

  1. Open the console and create the folder chapter01/recipe01.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
import (
"log"
"runtime"
)
const info = `
Application %s starting.
The binary was build by GO: %s`

func main() {
log.Printf(info, "Example", runtime.Version())
}

  1. Run the code by executing the go run main.go.
  2. See the output in the Terminal:

How it works...

The runtime package contains a lot of useful functions. To find out the Go runtime version, the Version function could be used. The documentation states that the function returns the hash of the commit, and the date or tag at the time of the binary build.

The Version function, in fact, returns the runtime/internal/sys .The Version constant. The constant itself is located in the $GOROOT/src/runtime/internal/sys/zversion.go file.

This .go file is generated by the go dist tool and the version is resolved by the findgoversion function in the go/src/cmd/dist/build.go file, as explained next.

The $GOROOT/VERSION takes priority. If the file is empty or does not exist, the $GOROOT/VERSION.cache file is used. If the $GOROOT/VERSION.cache is also not found, the tool tries to resolve the version by using the Git information, but in this case, you need to initialize the Git repository for the Go source.

Accessing program arguments

The most simple way to parameterize the program run is to use the command-line arguments as program parameters.

Simply, the parameterized program call could look like this: ./parsecsv user.csv role.csv. In this case, parsecsv is the name of the executed binary and user.csv and role.csv are the arguments, that modify the program call (in this case it refers to files to be parsed).

How to do it...

  1. Open the console and create the folder chapter01/recipe02.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
import (
"fmt"
"os"
)

func main() {

args := os.Args

// This call will print
// all command line arguments.
fmt.Println(args)

// The first argument, zero item from slice,
// is the name of the called binary.
programName := args[0]
fmt.Printf("The binary name is: %s \n", programName)

// The rest of the arguments could be obtained
// by omitting the first argument.
otherArgs := args[1:]
fmt.Println(otherArgs)

for idx, arg := range otherArgs {
fmt.Printf("Arg %d = %s \n", idx, arg) }
}
  1. Build the binary by executing go build -o test.
  2. Execute the command ./test arg1 arg2. (Windows users can run test.exe arg1 arg2).
  3. See the output in the Terminal:

How it works...

The Go standard library offers a few ways to access the arguments of the program call. The most generic way is to access the arguments by the Args variable from the OS package.

This way you can get all the arguments from the command line in a string slice. The advantage of this approach is that the number of arguments is dynamic and this way you can, for example, pass the names of the files to be processed by the program.

The preceding example just echoes all the arguments that are passed to the program. Finally, let's say the binary is called test and the program run is executed by the Terminal command ./test arg1 arg2.

In detail, the os.Args[0] will return ./test. The os.Args[1:] returns the rest of the arguments without the binary name. In the real world, it is better to not rely on the number of arguments passed to the program, but always check the length of the argument array. Otherwise, naturally, if the argument on a given index is not within the range, the program panics.

There's more...

If the arguments are defined as flags, -flag value, additional logic is needed to assign the value to the flag. In this case, there is a better way to parse these by using the flag package. This approach is part of the next recipe.

Creating a program interface with the flag package

The previous recipe describes how to access the program arguments by a very generic approach.

This recipe will provide a way of defining an interface via the program flags. This approach dominates systems based on GNU/Linux, BSD, and macOS. The example of the program call could be ls -l which will, on *NIX systems, list the files in a current directory.

The Go package for flag handling does not support flag combining like ls -ll, where there are multiple flags after a single dash. Each flag must be separate. The Go flag package also does not differentiate between long options and short ones. Finally, -flag and --flag are equivalent.

How to do it...

  1. Open the console and create the folder chapter01/recipe03.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
)

// Custom type need to implement
// flag.Value interface to be able to
// use it in flag.Var function.
type ArrayValue []string

func (s *ArrayValue) String() string {
return fmt.Sprintf("%v", *s)
}

func (a *ArrayValue) Set(s string) error {
*a = strings.Split(s, ",")
return nil
}

func main() {

// Extracting flag values with methods returning pointers
retry := flag.Int("retry", -1, "Defines max retry count")

// Read the flag using the XXXVar function.
// In this case the variable must be defined
// prior to the flag.
var logPrefix string
flag.StringVar(&logPrefix, "prefix", "", "Logger prefix")

var arr ArrayValue
flag.Var(&arr, "array", "Input array to iterate through.")

// Execute the flag.Parse function, to
// read the flags to defined variables.
// Without this call the flag
// variables remain empty.
flag.Parse()

// Sample logic not related to flags
logger := log.New(os.Stdout, logPrefix, log.Ldate)

retryCount := 0
for retryCount < *retry {
logger.Println("Retrying connection")
logger.Printf("Sending array %v\n", arr)
retryCount++
}
}
  1. Build the binary by executing the go build -o util.
  2. From the console, execute ./util -retry 2 -prefix=example -array=1,2.
  1. See the output in the Terminal:

How it works...

For the flag definition in code, the flag package defines two types of functions.

The first type is the simple name of the flag type such as Int. This function will return the pointer to the integer variable where the value of the parsed flag is.

The XXXVar functions are the second type. These provide the same functionality, but you need to provide the pointer to the variable. The parsed flag value will be stored in the given variable.

The Go library also supports a custom flag type. The custom type must implement the Value interface from the flag package.

As an example, let's say the flag retry defines the retry limit for reconnecting to the endpoint, the prefix flag defines the prefix of each row in a log, and the array is the array flag that will be send as an payload to server. The program call from the Terminal will look like ./util -retry 2 -prefix=example array=1,2.

The important part of the preceding code is the Parse() function which parses the defined flags from Args[1:]. The function must be called after all flags are defined and before the values are accessed.

The preceding code shows how to parse some data types from the command-line flags. Analogously, the other built-in types are parsed.

The last flag, array, demonstrates the definition of the custom type flag. Note that the ArrayType implements the Value interface from the flag package.

There's more...

The flag package contains more functions to design the interface with flags. It is worth reading the documentation for FlagSet.

By defining the new FlagSet, the arguments could be parsed by calling the myFlagset.Parse(os.Args[2:]). This way you can have flag subsets based on, for example, the first flag.

Getting and setting environment variables with default values

The previous recipe, Creating a program interface with the flag package, describes how to use flags as program parameters.

The other typical way of parameterization, especially for larger applications, is the configuration with the use of environment variables. Environment variables as a configuration option significantly simplify the deployment of the applications. These are also very common in cloud infrastructure.

Usually, the configuration of a database connection for a local and for an automated build environment is different.

If the configuration is defined by the environment variables, it is not necessary to change the application config files or even the application code. The exported environment variables (for example, DBSTRING) are all we need. It is also very practical to default the configuration if the environmental variable is not in place. This way, the life of the application developers is much easier.

This recipe will demonstrate how to read, set and unset the environment variable. It will also show you how to implement the default option if the variable is not set.

How to do it...

  1. Open the console and create the folder chapter01/recipe04.
  2. Navigate to the directory.
  1. Create the get.go file with the following content:
        package main

import (
"log"
"os"
)

func main() {
connStr := os.Getenv("DB_CONN")
log.Printf("Connection string: %s\n", connStr)
}
  1. Execute the code by calling DB_CONN=db:/user@example && go run get.go in the Terminal.
  2. See the output in the Terminal:
  1. Create the lookup.go file with the following content:
        package main

import (
"log"
"os"
)

func main() {

key := "DB_CONN"

connStr, ex := os.LookupEnv(key)
if !ex {
log.Printf("The env variable %s is not set.\n", key)
}
fmt.Println(connStr)
}
  1. Execute the code by calling unset DB_CONN && go run lookup.go in the Terminal.
  2. See the output in the Terminal:
  1. Create the main.go file with the following content:
        package main
import (
"log"
"os"
)

func main() {

key := "DB_CONN"
// Set the environmental variable.
os.Setenv(key, "postgres://as:[email protected]/pg?
sslmode=verify-full")
val := GetEnvDefault(key, "postgres://as:as@localhost/pg?
sslmode=verify-full")
log.Println("The value is :" + val)

os.Unsetenv(key)
val = GetEnvDefault(key, "postgres://as:[email protected]/pg?
sslmode=verify-full")
log.Println("The default value is :" + val)

}

func GetEnvDefault(key, defVal string) string {
val, ex := os.LookupEnv(key)
if !ex {
return defVal
}
return val
}
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works...

The environment variables are accessed by the Getenv and Setenv functions in the os package. The names of the functions are self-explanatory and do not need any further description.

There is one more useful function in the os package. The LookupEnv function provides two values as a result; the value of the variable, and the boolean value which defines if the variable was set or not in the environment.

The disadvantage of the os.Getenv function is that it returns an empty string, even in cases where the environment variable is not set.

This handicap could be overcome by the os.LookupEnv function, which returns the string as a value of the environment variable and the boolean value that indicates whether the variable was set or not.

To implement the retrieval of the environment variable or the default one, use the os.LookupEnv function. Simply, if the variable is not set, which means that the second returned value is false, then the default value is returned. The use of the function is part of step 9.

Retrieving the current working directory

Another useful source of information for the application is the directory, where the program binary is located. With this information, the program can access the assets and files collocated with the binary file.

This recipe is using the solution for Go since version 1.8. This one is the preferred one.

How to do it...

  1. Open the console and create the folder chapter01/recipe05.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

import (
"fmt"
"os"
"path/filepath"
)

func main() {
ex, err := os.Executable()
if err != nil {
panic(err)
}

// Path to executable file
fmt.Println(ex)

// Resolve the direcotry
// of the executable
exPath := filepath.Dir(ex)
fmt.Println("Executable path :" + exPath)

// Use EvalSymlinks to get
// the real path.
realPath, err := filepath.EvalSymlinks(exPath)
if err != nil {
panic(err)
}
fmt.Println("Symlink evaluated:" + realPath)
}
  1. Build the binary by the command go build -o binary.
  2. Execute the binary by the Terminal call ./binary.
  3. See the output. It should display the absolute path on your machine:

How it works...

Since Go 1.8, the Executable function from the os package is the preferred way of resolving the path of the executable. The Executable function returns the absolute path of the binary that is executed (unless the error is returned).

To resolve the directory from the binary path, the Dir from the filepath package is applied. The only pitfall of this is that the result could be the symlink or the path it pointed to.

To overcome this unstable behavior, the EvalSymlinks from the filepath package could be applied to the resultant path. With this hack, the returned value would be the real path of the binary.

The information about the directory where the binary is located could be obtained with the use of the Executable function in the os library.

Note that if the code is run by the command go run, the actual executable is located in a temporary directory.

Getting the current process PID

Getting to know the PID of the running process is useful. The PID could be used by OS utilities to find out the information about the process itself. It is also valuable to know the PID in case of process failure, so you can trace the process behavior across the system in system logs, such as /var/log/messages, /var/log/syslog.

This recipe shows you how to use the os package to obtain a PID of the executed program, and use it with the operating system utility to obtain some more information.

How to do it...

  1. Open the console and create the folder chapter01/recipe06.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

import (
"fmt"
"os"
"os/exec"
"strconv"
)

func main() {

pid := os.Getpid()
fmt.Printf("Process PID: %d \n", pid)

prc := exec.Command("ps", "-p", strconv.Itoa(pid), "-v")
out, err := prc.Output()
if err != nil {
panic(err)
}

fmt.Println(string(out))
}
  1. Run the code by executing the go run main.go.
  1. See the output in the Terminal:

How it works...

The function Getpid from the os package returns the PID of a process. The sample code shows how to get more information on the process from the operating system utility ps.

It could be useful to print the PID at the start of the application, so at the time of the crash, the cause could also be investigated by the retrieved PID.

Handling operating system signals

Signals are the elementary way the operating systems communicate with the running process. Two of the most usual signals are called SIGINT and SIGTERM. These cause the program to terminate.

There are also signals such as SIGHUP. SIGHUP indicates that the terminal which called the process was closed and, for example, the program could decide to move to the background.

Go provides a way of handling the behavior in case the application received the signal. This recipe will provide an example of implementing the handling.

How to do it...

  1. Open the console and create the folder chapter01/recipe07.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

import (
"fmt"
"os"
"os/signal"
"syscall"
)

func main() {

// Create the channel where the received
// signal would be sent. The Notify
// will not block when the signal
// is sent and the channel is not ready.
// So it is better to
// create buffered channel.
sChan := make(chan os.Signal, 1)

// Notify will catch the
// given signals and send
// the os.Signal value
// through the sChan.
// If no signal specified in
// argument, all signals are matched.
signal.Notify(sChan,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)

// Create channel to wait till the
// signal is handled.
exitChan := make(chan int)
go func() {
signal := <-sChan
switch signal {
case syscall.SIGHUP:
fmt.Println("The calling terminal has been closed")
exitChan <- 0

case syscall.SIGINT:
fmt.Println("The process has been interrupted by CTRL+C")
exitChan <- 1

case syscall.SIGTERM:
fmt.Println("kill SIGTERM was executed for process")
exitChan <- 1

case syscall.SIGQUIT:
fmt.Println("kill SIGQUIT was executed for process")
exitChan <- 1
}
}()

code := <-exitChan
os.Exit(code)
}
  1. Run the code by executing go run main.go.
  2. Send the SIGINT signal to the application by pressing CTRL + C.
  3. See the output:

How it works...

In an application, where the resources are acquired, a resource leak could happen in the case of an instant termination. It is better to handle the signals and take some necessary steps to release the resources. The preceding code shows the concept of how to do that.

The Notify function from the signal package would be the one that helps us to handle the received signals.

If no signal is specified as an argument in a Notify function, the function will catch all possible signals.

Note that the Notify function of the signal package is communicating with the goroutine by the sChan channel. Notify then catches the defined signals and sends these to goroutine to be handled. Finally, exitChan is used to resolve the exit code of the process.

The important information is that the Notify function will not block the signal if the assigned channel is not ready. This way the signal could be missed. To avoid missing the signal, it is better to create the buffered channel.

Note that the SIGKILL and SIGSTOP signals may not be caught by the Notify function, thus it is not possible to handle these.

Calling an external process

The Go binary could also be used as a tool for various utilities and with use of go run as a replacement for the bash script. For these purposes, it is usual that the command-line utilities are called.

In this recipe, the basics of how to execute and handle the child process will be provided.

Getting ready

Test if the following commands work in your Terminal:

  1. Test if the ls (dir for Windows) command exists in your $PATH.
  2. You should be able to execute the ls (dir in Windows) command in your Terminal.

How to do it...

The following steps cover the solution:

  1. Open the console and create the folder chapter01/recipe08.
  2. Navigate to the directory.
  3. Create the run.go file with the following content:
        package main

import (
"bytes"
"fmt"
"os/exec"
)

func main() {

prc := exec.Command("ls", "-a")
out := bytes.NewBuffer([]byte{})
prc.Stdout = out
err := prc.Run()
if err != nil {
fmt.Println(err)
}

if prc.ProcessState.Success() {
fmt.Println("Process run successfully with output:\n")
fmt.Println(out.String())
}
}
  1. Run the code by executing go run run.go.
  1. See the output in the Terminal:
  1. Create the start.go file with the following content:
        package main

import (
"fmt"
"os/exec"
)

func main() {

prc := exec.Command("ls", "-a")
err := prc.Start()
if err != nil {
fmt.Println(err)
}

prc.Wait()

if prc.ProcessState.Success() {
fmt.Println("Process run successfully with output:\n")
fmt.Println(out.String())
}
}
  1. Run the code by executing go run start.go.
  2. See the output in Terminal:

How it works...

The Go standard library provides a simple way of calling the external process. This could be done by the Command function of the os/exec package.

The simplest way is to create the Cmd struct and call the Run function. The Run function executes the process and waits until it completes. If the command exited with an error, the err value is not null.

This is more suitable for calling the OS utils and tools, so the program does not hang too long.

The process could be executed asynchronously too. This is done by calling the Start method of the Cmd structure. In this case, the process is executed, but the main goroutine does not wait until it ends. The Wait method could be used to wait until the process ends. After the Wait method finishes, the resources of the process are released.

This approach is more suitable for executing long-running processes and services that the program depends on.

See also

This recipe describes how to simply execute the child process. There are Retrieve child process information and Reading/writing from the child process recipes in this chapter that also provide the steps on how to read from and write to the child process, and get useful information about the process.

Retrieving child process information

The recipe Calling an external process describes how to call the child process, synchronously and asynchronously. Naturally, to handle the process behavior you need to find out more about the process. This recipe shows how to obtain the PID and elementary information about the child process after it terminates.

The information about the running process could be obtained only via the syscall package and it is highly platform-dependent.

Getting ready

Test if the sleep (timeout for Windows) command exists in the Terminal.

How to do it...

  1. Open the console and create the folder chapter01/recipe09.
  2. Navigate to the directory.
  3. Create the main_running.go file with the following content:
        package main

import (
"fmt"
"os/exec"
"runtime"
)

func main() {

var cmd string
if runtime.GOOS == "windows" {
cmd = "timeout"
} else {
cmd = "sleep"
}
proc := exec.Command(cmd, "1")
proc.Start()

// No process state is returned
// till the process finish.
fmt.Printf("Process state for running process: %v\n",
proc.ProcessState)

// The PID could be obtain
// event for the running process
fmt.Printf("PID of running process: %d\n\n",
proc.Process.Pid)
}
  1. Run the code by executing go run main_running.go.
  2. See the output in the Terminal:
  1. Create the main.go file with the following content:
        func main() {

var cmd string
if runtime.GOOS == "windows" {
cmd = "timeout"
} else {
cmd = "sleep"
}

proc := exec.Command(cmd, "1")
proc.Start()

// Wait function will
// wait till the process ends.
proc.Wait()

// After the process terminates
// the *os.ProcessState contains
// simple information
// about the process run
fmt.Printf("PID: %d\n", proc.ProcessState.Pid())
fmt.Printf("Process took: %dms\n",
proc.ProcessState.SystemTime()/time.Microsecond)
fmt.Printf("Exited sucessfuly : %t\n",
proc.ProcessState.Success())
}
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works...

The os/exec standard library provides the way to execute the process. Using Command, the Cmd structure is returned. The Cmd provides the access to process the representation. When the process is running, you can only find out the PID.

There is only a little information that you can retrieve about the process. But by retrieving the PID of the process, you are able to call the utilities from the OS to get more information.

Remember that it is possible to obtain the PID of the child process, even if it is running. On the other hand, the ProcessState structure of the os package is available, only after the process terminates.

See also

There are Reading/writing from the child process and Calling an external process recipes in this chapter that are related to process handling.

Reading/writing from the child process

Every process, that is executed, has the standard output, input and error output. The Go standard library provides the way to read and write to these.

This recipe will walk through the approaches on how to read the output and write to the input of the child process.

Getting ready

Verify if the following commands work in the Terminal:

  1. Test if the ls (dir for Windows) command exists in the Terminal.
  2. You should be able to execute the ls (dir in Windows) command in your Terminal.

How to do it...

  1. Open the console and create the folder chapter01/recipe10.
  2. Navigate to the directory.
  3. Create the main_read_output.go file with the following content:
       package main

import (
"fmt"
"os/exec"
"runtime"
)

func main() {

var cmd string

if runtime.GOOS == "windows" {
cmd = "dir"
} else {
cmd = "ls"
}

proc := exec.Command(cmd)

// Output will run the process
// terminates and returns the standard
// output in a byte slice.
buff, err := proc.Output()

if err != nil {
panic(err)
}

// The output of child
// process in form
// of byte slice
// printed as string
fmt.Println(string(buff))

}
  1. Run the code by executing go run main_read_output.go.
  2. See the output in the Terminal:
  1. Create the main_read_stdout.go file with the following content:
        package main

import (
"bytes"
"fmt"
"os/exec"
"runtime"
)

func main() {

var cmd string

if runtime.GOOS == "windows" {
cmd = "dir"
} else {
cmd = "ls"
}

proc := exec.Command(cmd)

buf := bytes.NewBuffer([]byte{})

// The buffer which implements
// io.Writer interface is assigned to
// Stdout of the process
proc.Stdout = buf

// To avoid race conditions
// in this example. We wait till
// the process exit.
proc.Run()

// The process writes the output to
// to buffer and we use the bytes
// to print the output.
fmt.Println(string(buf.Bytes()))

}
  1. Run the code by executing go run main_read_stdout.go.
  2. See the output in the Terminal:
  1. Create the main_read_read.go file with the following content:
        package main

import (
"bufio"
"context"
"fmt"
"os/exec"
"time"
)

func main() {
cmd := "ping"
timeout := 2 * time.Second

// The command line tool
// "ping" is executed for
// 2 seconds
ctx, _ := context.WithTimeout(context.TODO(), timeout)
proc := exec.CommandContext(ctx, cmd, "example.com")

// The process output is obtained
// in form of io.ReadCloser. The underlying
// implementation use the os.Pipe
stdout, _ := proc.StdoutPipe()
defer stdout.Close()

// Start the process
proc.Start()

// For more comfortable reading the
// bufio.Scanner is used.
// The read call is blocking.
s := bufio.NewScanner(stdout)
for s.Scan() {
fmt.Println(s.Text())
}
}
  1. Run the code by executing go run main_read.go.
  2. See the output in the Terminal:
  1. Create the sample.go file with the following content:
        package main

import (
"bufio"
"fmt"
"os"
)

func main() {
sc := bufio.NewScanner(os.Stdin)

for sc.Scan() {
fmt.Println(sc.Text())
}
}
  1. Create the main.go file with the following content:
        package main

import (
"bufio"
"fmt"
"io"
"os/exec"
"time"
)

func main() {
cmd := []string{"go", "run", "sample.go"}

// The command line tool
// "ping" is executed for
// 2 seconds
proc := exec.Command(cmd[0], cmd[1], cmd[2])

// The process input is obtained
// in form of io.WriteCloser. The underlying
// implementation use the os.Pipe
stdin, _ := proc.StdinPipe()
defer stdin.Close()

// For debugging purposes we watch the
// output of the executed process
stdout, _ := proc.StdoutPipe()
defer stdout.Close()

go func() {
s := bufio.NewScanner(stdout)
for s.Scan() {
fmt.Println("Program says:" + s.Text())
}
}()

// Start the process
proc.Start()

// Now the following lines
// are written to child
// process standard input
fmt.Println("Writing input")
io.WriteString(stdin, "Hello\n")
io.WriteString(stdin, "Golang\n")
io.WriteString(stdin, "is awesome\n")

time.Sleep(time.Second * 2)

proc.Process.Kill()

}
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works...

The Cmd structure of the os/exec package provides the functions to access the output/input of the process. There are a few approaches to read the output of the process.

One of the simplest ways to read the process output is to use the Output or CombinedOutput method of the Cmd structure (gets Stderr and Stdout). While calling this function, the program synchronously waits till the child process terminates and then returns the output to a byte buffer.

Besides the Output and OutputCombined methods, the Cmd structure provides the Stdout property, where the io.Writer could be assigned. The assigned writer then serves as a destination for the process output. It could be a file, byte buffer or any type implementing the io.Writer interface.

The last approach to read the process output is to get the io.Reader from the Cmd structure by calling the StdoutPipe method. The StdoutPipe method creates the pipe between the Stdout, where the process writes the output, and provides Reader which works as the interface for the program to read the process output. This way the output of the process is piped to the retrieved io.Reader .

Writing to a process stdin works the same way. Of all the options, the one with io.Writer will be demonstrated.

As could be seen, there are a few ways to read and write from the child process. The use of stderr and stdin is almost the same as described in steps 6-7. Finally, the approach of how to access the input/output could be divided this way:

  • Synchronous (wait until the process ends and get the bytes): The Output and CombinedOutput methods of Cmd are used.
  • IO: The output or input are provided in the form of io.Writer/Reader. The XXXPipe and StdXXX properties are the right ones for this approach.

The IO type is more flexible and could also be used asynchronously.

Shutting down the application gracefully

Servers and daemons are the programs that run for a long time (typically days or even weeks). These long-running programs usually allocate resources (database connections, network sock) at the start and keep these resources as long as they exist. If such a process is killed and the shutdown is not handled properly, a resource leak could happen. To avoid that behavior, the so-called graceful shutdown should be implemented.

Graceful, in this case, means that the application catches the termination signal, if possible, and tries to clean up and release the allocated resources before it terminates. This recipe will show you how to implement the graceful shutdown.

The recipe, Handling operating system signals describes the catching of OS signals. The same approach will be used for implementing the graceful shutdown. Before the program terminates, it will clean up and carry out some other activities.

How to do it...

  1. Open the console and create the folder chapter01/recipe11.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

import (
"fmt"
"io"
"log"
"os"
"os/signal"
"syscall"
"time"
)

var writer *os.File

func main() {

// The file is opened as
// a log file to write into.
// This way we represent the resources
// allocation.
var err error
writer, err = os.OpenFile(fmt.Sprintf("test_%d.log",
time.Now().Unix()), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
panic(err)
}

// The code is running in a goroutine
// independently. So in case the program is
// terminated from outside, we need to
// let the goroutine know via the closeChan
closeChan := make(chan bool)
go func() {
for {
time.Sleep(time.Second)
select {
case <-closeChan:
log.Println("Goroutine closing")
return
default:
log.Println("Writing to log")
io.WriteString(writer, fmt.Sprintf("Logging access
%s\n", time.Now().String()))
}

}
}()

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGINT)

// This is blocking read from
// sigChan where the Notify function sends
// the signal.
<-sigChan

// After the signal is received
// all the code behind the read from channel could be
// considered as a cleanup.
// CLEANUP SECTION
close(closeChan)
releaseAllResources()
fmt.Println("The application shut down gracefully")
}

func releaseAllResources() {
io.WriteString(writer, "Application releasing
all resources\n")
writer.Close()
}
  1. Run the code by executing go run main.go.
  2. Press CTRL + C to send a SIGINT signal.
  3. Wait until the Terminal output looks like this:
  1. The recipe11 folder should also contain a file called test_XXXX.log, which contains lines like this:

How it works...

The reading from a sigChan is blocking so the program keeps running until the Signal is sent through the channel. The sigChan is the channel where the Notify function sends the signals.

The main code of the program runs in a new goroutine. This way, the work continues while the main function is blocked on the sigChan. Once the signal from operation system is sent to process, the sigChan receives the signal and the code below the line where the reading from the sigChan channel is executed. This code section could be considered as the cleanup section.

Note that the step 7 terminal output contains the final log, Application releasing all resources, which is part of the cleanup section.

See also

A detailed description of how the signal catching works is in the recipe Handling operating system signals.

File configuration with functional options

This recipe is not directly related to the Go standard library but includes how to handle an optional configuration for your application. The recipe will use the functional options pattern in a real case with a file configuration.

How to do it...

  1. Open the console and create the folder chapter01/recipe12.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

import (
"encoding/json"
"fmt"
"os"
)

type Client struct {
consulIP string
connString string
}

func (c *Client) String() string {
return fmt.Sprintf("ConsulIP: %s , Connection String: %s",
c.consulIP, c.connString)
}

var defaultClient = Client{
consulIP: "localhost:9000",
connString: "postgres://localhost:5432",
}

// ConfigFunc works as a type to be used
// in functional options
type ConfigFunc func(opt *Client)

// FromFile func returns the ConfigFunc
// type. So this way it could read the configuration
// from the json.
func FromFile(path string) ConfigFunc {
return func(opt *Client) {
f, err := os.Open(path)
if err != nil {
panic(err)
}
defer f.Close()
decoder := json.NewDecoder(f)

fop := struct {
ConsulIP string `json:"consul_ip"`
}{}
err = decoder.Decode(&fop)
if err != nil {
panic(err)
}
opt.consulIP = fop.ConsulIP
}
}

// FromEnv reads the configuration
// from the environmental variables
// and combines them with existing ones.
func FromEnv() ConfigFunc {
return func(opt *Client) {
connStr, exist := os.LookupEnv("CONN_DB")
if exist {
opt.connString = connStr
}
}
}

func NewClient(opts ...ConfigFunc) *Client {
client := defaultClient
for _, val := range opts {
val(&client)
}
return &client
}

func main() {
client := NewClient(FromFile("config.json"), FromEnv())
fmt.Println(client.String())
}
  1. In the same folder, create the file config.json with content:
        {
"consul_ip":"127.0.0.1"
}
  1. Execute the code by the command CONN_DB=oracle://local:5921 go run main.go.
  2. See the output:

How it works...

The core concept of the functional options pattern is that the configuration API contains the functional parameters. In this case, the NewClient function accepts a various number of ConfigFunc arguments, which are then applied one by one on the defaultClient struct. This way, the default configuration is modified with huge flexibility.

See the FromFile and FromEnv functions, which return the ConfigFunc, that is in fact, accessing the file or environmental variables.

Finally, you can check the output which applied both the configuration options and resulting Client struct that contains the values from the file and environmental variables.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Develop high quality, fast and portable applications by leveraging the power of Go Standard Library.
  • Practical recipes that will help you work with the standard library algorithms to boost your productivity as a Go developer.
  • Compose your own algorithms without forfeiting the simplicity and elegance of the Standard Library.

Description

Google's Golang will be the next talk of the town, with amazing features and a powerful library. This book will gear you up for using golang by taking you through recipes that will teach you how to leverage the standard library to implement a particular solution. This will enable Go developers to take advantage of using a rock-solid standard library instead of third-party frameworks. The book begins by exploring the functionalities available for interaction between the environment and the operating system. We will explore common string operations, date/time manipulations, and numerical problems. We'll then move on to working with the database, accessing the filesystem, and performing I/O operations. From a networking perspective, we will touch on client and server-side solutions. The basics of concurrency are also covered, before we wrap up with a few tips and tricks. By the end of the book, you will have a good overview of the features of the Golang standard library and what you can achieve with them. Ultimately, you will be proficient in implementing solutions with powerful standard libraries.

Who is this book for?

This book is for Go developers who would like to explore the power of Golang and learn how to use the Go standard library for various functionalities. The book assumes basic Go programming knowledge.

What you will learn

  • Access environmental variables
  • Execute and work with child processes
  • Manipulate strings by performing operations such as search, concatenate, and so on
  • Parse and format the output of date/time information
  • Operate on complex numbers and effective conversions between different number formats and bases
  • Work with standard input and output
  • Handle filesystem operations and file permissions
  • Create TCP and HTTP servers, and access those servers with a client
  • Utilize synchronization primitives
  • Test your code
Estimated delivery fee Deliver to Australia

Economy delivery 7 - 10 business days

AU$19.95

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Feb 27, 2018
Length: 340 pages
Edition : 1st
Language : English
ISBN-13 : 9781788475273
Category :
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Australia

Economy delivery 7 - 10 business days

AU$19.95

Product Details

Publication date : Feb 27, 2018
Length: 340 pages
Edition : 1st
Language : English
ISBN-13 : 9781788475273
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
AU$24.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
AU$249.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just AU$5 each
Feature tick icon Exclusive print discounts
AU$349.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just AU$5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total AU$ 197.97
Security with Go
AU$60.99
Distributed Computing with Go
AU$60.99
Go Standard Library Cookbook
AU$75.99
Total AU$ 197.97 Stars icon
Banner background image

Table of Contents

12 Chapters
Interacting with the Environment Chevron down icon Chevron up icon
Strings and Things Chevron down icon Chevron up icon
Dealing with Numbers Chevron down icon Chevron up icon
Once Upon a Time Chevron down icon Chevron up icon
In and Out Chevron down icon Chevron up icon
Discovering the Filesystem Chevron down icon Chevron up icon
Connecting the Network Chevron down icon Chevron up icon
Working with Databases Chevron down icon Chevron up icon
Come to the Server Side Chevron down icon Chevron up icon
Fun with Concurrency Chevron down icon Chevron up icon
Tips and Tricks Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Rating distribution
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
(1 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 100%
Leam Hall Oct 15, 2018
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
Really didn't get fully past the first chapter. The author has an engaging style and if Packt had done a decent job editing and fixing this could be a classic. When contacted they wanted me to spend time logging all their problems for free. Since there were more than a dozen in Chapter 1 I figured it wasn't worth the time.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela