CodeBlanks Logo Icon CodeBlanks

Summary - flag package

Combine flag and os.Args to write a CLI tool.

CLI summary

A good command-line tool: flag for options (automatic help with -h), flag.Args() for positional arguments, separate FlagSets for subcommands, reading stdin when no file is given, writing results to stdout and errors to stderr, and exit code 0 on success, 1 on error and 2 on usage errors.

func main() {
    if err := run(os.Args[1:], os.Stdin, os.Stdout); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Signals and cancellation

A long-running tool should clean up temporary files when stopped with Ctrl+C. signal.NotifyContext turns the signal into context cancellation:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
if err := isle(ctx); errors.Is(err, context.Canceled) {
    fmt.Fprintln(os.Stderr, "iptal edildi")
    os.Exit(130)
}

130 is the conventional exit code for programs interrupted with Ctrl+C.

Practice The run function

Write run(args []string, in io.Reader, out io.Writer) error: parse the -n option (int, default 1) with its own FlagSet, read the first line of the input with a bufio.Scanner and write it to out n times. Call it with run([]string{"-n=2"}, strings.NewReader("go\n"), os.Stdout).

Starter code

main.go
package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    // buraya yaz
}

Sample solution

main.go
package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "os"
    "strings"
)

func run(args []string, in io.Reader, out io.Writer) error {
    fs := flag.NewFlagSet("tekrar", flag.ContinueOnError)
    n := fs.Int("n", 1, "tekrar sayısı")
    if err := fs.Parse(args); err != nil {
        return err
    }
    sc := bufio.NewScanner(in)
    if !sc.Scan() {
        return sc.Err()
    }
    for i := 0; i < *n; i++ {
        fmt.Fprintln(out, sc.Text())
    }
    return nil
}

func main() {
    if err := run([]string{"-n=2"}, strings.NewReader("go\n"), os.Stdout); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Expected output

go
go

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why is the <code>run(args, stdin, stdout)</code> pattern common?

  • The flag package requires it
  • It needs less code
  • The whole tool can be tested without touching the os package Correct answer
  • It runs faster

Explanation: Because the arguments, input and output are parameters, tests pass a slice, a strings.Reader and a bytes.Buffer.

2 List the options

Question
flag.Usage = func() {
    fmt.Fprintln(os.Stderr, "kullanım: app [seçenekler]")
    flag.____()
}
Answer
flag.Usage = func() {
    fmt.Fprintln(os.Stderr, "kullanım: app [seçenekler]")
    flag.PrintDefaults()
}

Explanation: PrintDefaults prints every option with its description and default.

3 Group summary

Term Meaning
flag.Int Option definition
flag.Args() Positional arguments
FlagSet Subcommand
os.Exit(2) Usage error

All lessons in Intermediate