Go
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.
Exercise 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).
Show example solution
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)
}
}
Test yourself
-
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
- It runs faster
Show answer
The whole tool can be tested without touching the os package — Because the arguments, input and output are parameters, tests pass a slice, a strings.Reader and a bytes.Buffer.
-
List the options
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "kullanım: app [seçenekler]")
flag.____()
}
Show answer
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "kullanım: app [seçenekler]")
flag.PrintDefaults()
}
-
Group summary
- flag.Int
- Option definition
- flag.Args()
- Positional arguments
- FlagSet
- Subcommand
- os.Exit(2)
- Usage error
Next lesson: Challenge 1 - Concurrency