ruk·si

🐹 Go
Command Line Programs

Updated at 2016-08-19 00:13

This note contains information about creating programs that can be customized when launched from the command line and how to handle other command line related events.

You can get the raw command that was used to start the program with os.Args.

package main

import (
    "fmt"
    "os"
)

func main() {
    // Calling... main f1rst s3cond th1rd

    arg := os.Args[3]
    fmt.Println(arg) // => th1rd

    argsWithProg := os.Args
    fmt.Println(argsWithProg) // => [main f1rst s3cond th1rd]

    argsWithoutProg := os.Args[1:]
    fmt.Println(argsWithoutProg) // => [f1rst s3cond th1rd]
}

Flags are a common way to specify program parameters. For example, -a in ls -a is a flag.

package main

import (
    "flag"
    "fmt"
)

func main() {
    // Flag name, default value, description.
    // Description is show when the program is called with incorrect parameters.
    swordPtr := flag.String("sword", "katana", "name of your sword of choice")
    twkdPtr := flag.Bool("tweaked", false, "is the command tweaked")
    warriorPtr := flag.Int("warriors", 1, "number of warriors you have")

    // Assigning flag value to an existing variable.
    var shield string
    flag.StringVar(&shield, "shield", "buckler", "name of your shield")

    flag.Parse() // After flags are declared, use flag.Parse().

    // Calling...
    // main -sword excalibur -tweaked -warriors 5 -shield "tower shield"
    fmt.Println("sword:", *swordPtr) // sword: excalibur
    fmt.Println("tweaked:", *twkdPtr) // tweaked: true
    fmt.Println("warriors:", *warriorPtr) // warriors: 5
    fmt.Println("shield:", shield) // shield: tower shield
}

os package allows reading and writing environmental variables.

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    errSet := os.Setenv("MAN", "Roger Moore")
    if errSet != nil {
        panic(errSet)
    }

    manName := os.Getenv("MAN")     // Roger Moore
    womanName := os.Getenv("WOMAN") // Empty string
    fmt.Println("MAN:", manName)
    fmt.Println("WOMAN:", womanName)

    // List all env variables.
    for _, variable := range os.Environ() {
        pair := strings.Split(variable, "=")
        if pair[0] == "Path" {
            fmt.Printf("%s=%s\n", pair[0], pair[1])
        }
    }
}

Define and parse flags in main func. Read command line flags but fall back to env variables. Libraries should use constructor parameters or configuration object.

//                (required1, required2, optionals)
foo, err := newFoo(*fooKey, fooConfig{
    Bar:    bar,
    Period: 100 * time.Millisecond,
    Output: nil,
})
if err != nil {
    log.Fatal(err)
}

Signals are one way that operating systems communicate with running programs. Any non-trivial program should handle clean up after termination signals.

package main

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

func main() {
    signalChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
    //signal.Notify(signalChan, syscall.SIGSTOP)
    // `syscall` has around 30 error signals listed, only ones you cannot
    // intercept are SIGKILL (immediate termination) and
    // SIGSTOP (immediate pause for later resumption).
    // Here are the most important ones.
    // SIGINT = program should clean up files, sockets and processes (Ctrl+C).
    // SIGTERM = basically the same as SIGINT.
    // SIGQUIT = program should clean up only what is necessary, for debugging.
    // SIGKILL = program is not allowed to clean up.

    quitChannel := make(chan bool, 1)
    go func() {
        sig := <-signalChan
        fmt.Println("received signal:", sig)
        close(quitChannel)
    }()

    fmt.Println("waiting signal")
    <-quitChannel
    fmt.Println("exiting")
}

Sources