OS Process
Examples in
Go
Command-line arguments are a common way to parameterize execution of programs.
package main
import (
"fmt"
"os"
)
func main() {
// os.Args provides access to raw command-line arguments.
// Note that the first value in this slice is the path to the program,
// and os.Args[1:] holds the arguments to the program.
argsWithProg := os.Args
argsWithoutProg := os.Args[1:]
// You can get individual args with normal indexing.
arg := os.Args[3]
fmt.Println(argsWithProg)
fmt.Println(argsWithoutProg)
fmt.Println(arg)
}
Go provides a flag package supporting basic command-line flag parsing. We’ll use this package to implement our example command-line program.
package main
import (
"flag"
"fmt"
)
func main() {
// Basic flag declarations are available for string, integer, and boolean options.
// Here we declare a string flag word with a default value "foo" and a short description.
// This flag.String function returns a string pointer (not a string value);
// we’ll see how to use this pointer below.
wordPtr := flag.String("word", "foo", "a string")
// This declares numb and fork flags, using a similar approach to the word flag.
numbPtr := flag.Int("numb", 42, "an int")
boolPtr := flag.Bool("fork", false, "a bool")
// It’s also possible to declare an option that uses an existing var declared elsewhere in the program.
// Note that we need to pass in a pointer to the flag declaration function.
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
// Once all flags are declared, call flag.Parse() to execute the command-line parsing.
flag.Parse()
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *boolPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}
Last Run
:
word: foo
numb: 42
fork: false
svar: bar
tail: []
The flag package lets us easily define simple subcommands that have their own flags.
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// We declare a subcommand using the NewFlagSet function,
// and proceed to define new flags specific for this subcommand.
fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
fooEnable := fooCmd.Bool("enable", false, "enable")
fooName := fooCmd.String("name", "", "name")
// For a different subcommand we can define different supported flags.
barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
barLevel := barCmd.Int("level", 0, "level")
if len(os.Args) < 2 {
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}
switch os.Args[1] {
// For every subcommand, we parse its own flags and have access to trailing positional arguments.
case "foo":
fooCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'foo'")
fmt.Println(" enable:", *fooEnable)
fmt.Println(" name:", *fooName)
fmt.Println(" tail:", fooCmd.Args())
case "bar":
barCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'bar'")
fmt.Println(" level:", *barLevel)
fmt.Println(" tail:", barCmd.Args())
default:
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}
}
package main
import (
"os"
)
func main() {
os.Setenv("FOO", "1")
}
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("FOO:", os.Getenv("FOO"))
}
package main
import (
"fmt"
"os"
"strings"
)
func main() {
// Use os.Environ to list all key/value pairs in the environment.
// This returns a slice of strings in the form KEY=value.
// You can strings.SplitN them to get the key and value. Here we print all the keys.
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Println(pair[0])
}
}
package main
import (
"fmt"
"io/ioutil"
"os/exec"
)
func main() {
// We’ll start with a simple command that takes no arguments or input and just prints something to stdout.
// The exec.Command helper creates an object to represent this external process.
dateCmd := exec.Command("date")
// .Output is another helper that handles the common case of running a command, waiting for it to finish, and collecting its output.
// If there were no errors, dateOut will hold bytes with the date info.
dateOut, err := dateCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> date")
fmt.Println(string(dateOut))
// Next we’ll look at a slightly more involved case where we pipe data to the external process
// on its stdin and collect the results from its stdout.
grepCmd := exec.Command("grep", "hello")
// Here we explicitly grab input/output pipes, start the process, write some input to it,
// read the resulting output, and finally wait for the process to exit.
grepIn, _ := grepCmd.StdinPipe()
grepOut, _ := grepCmd.StdoutPipe()
grepCmd.Start()
grepIn.Write([]byte("hello grep\ngoodbye grep"))
grepIn.Close()
grepBytes, _ := ioutil.ReadAll(grepOut)
grepCmd.Wait()
fmt.Println("> grep hello")
fmt.Println(string(grepBytes))
// Note that when spawning commands we need to provide an explicitly delineated command and argument array, vs. being able to just pass in one command-line string.
// If you want to spawn a full command with a string, you can use bash’s -c option:
lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
lsOut, err := lsCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> ls -a -l -h")
fmt.Println(string(lsOut))
}
Calling exec completely replaces the current Go process with another (perhaps non-Go) one.
package main
import (
"os"
"os/exec"
"syscall"
)
func main() {
// For our example we’ll exec ls. Go requires an absolute path to the binary we want to execute,
// so we’ll use exec.LookPath to find it (probably /bin/ls).
binary, lookErr := exec.LookPath("ls")
if lookErr != nil {
panic(lookErr)
}
// Exec requires arguments in slice form (as apposed to one big string).
// We’ll give ls a few common arguments.
// Note that the first argument should be the program name.
args := []string{"ls", "-a", "-l", "-h"}
// Exec also needs a set of environment variables to use.
// Here we just provide our current environment.
env := os.Environ()
// Here’s the actual syscall.Exec call.
// If this call is successful, the execution of our process will end here and be replaced by the /bin/ls -a -l -h process.
// If there is an error we’ll get a return value.
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
panic(execErr)
}
}
Last Run
:
total 12K
drwx------ 2 runner runner 4.0K Aug 24 17:09 .
drwxrwxrwt 1 root root 4.0K Aug 24 17:09 ..
-rw-r--r-- 1 runner runner 1.1K Aug 24 17:09 main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
// Go signal notification works by sending os.Signal values on a channel.
// We’ll create a channel to receive these notifications (we’ll also make one to notify us when the program can exit).
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
// signal.Notify registers the given channel to receive notifications of the specified signals.
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// This goroutine executes a blocking receive for signals.
// When it gets one it’ll print it out and then notify the program that it can finish.
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
Use os.Exit to immediately exit with a given status.
package main
import (
"fmt"
"os"
)
func main() {
// defers will not be run when using os.Exit, so this fmt.Println will never be called.
defer fmt.Println("!")
os.Exit(3)
}