A CLI needs more than transformation logic: it must read inputs, write results, inspect its environment, and signal failure. Go’s os package provides a platform-independent interface to those operating-system functions, so application code can use the same interface on Windows, macOS, and Linux. section 10.4
Read a word-count input file
Use os.ReadFile(filename) to load a file into memory, check the returned error, then convert the bytes to a string for strings.Fields. To choose the file at launch rather than editing the program, take its name from os.Args[1]; os.Args[0] is the command itself, so check that an argument was supplied before indexing. section 2.6.1 section 2.7.1
For a larger CLI, os also handles the boundary between processing code and the system:
- Use os.Stdin for input, os.Stdout for normal output, and os.Stderr for diagnostics. An io.Reader or io.Writer can then refer either to a standard stream or to an opened file. section 10.4
- Use os.Open and os.Create for selected file paths; close opened files when finished. os.Stat can check that an input exists, is regular, and is nonempty before opening it. section 10.4
- Use os.LookupEnv or os.Getenv for environment-based settings. Use os.Exit(0) for success or a nonzero status for failure, but remember that os.Exit skips deferred cleanup. section 10.4
Listing 10.13 Using flags as guards on our application
func main() {
inputFlag := flag.String("f", "", "Input file path")
outputFlag := flag.String("o", "", "Output file path")
...
var input io.Reader = os.Stdin
var output io.Writer = os.Stdout
if inputFlag != nil && *inputFlag != "" {
file, err := os.Open(*inputFlag) #1
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening input file: %v\n", err)
os.Exit(1)
}
defer file.Close()
input = file
}
if outputFlag != nil && *outputFlag != "" {
outFile, err := os.Create(*outputFlag) #2
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err)
os.Exit(1)
}
defer outFile.Close()
output = outFile
}
// Pass to process function
if err := process(input, output); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
The file-selection example starts with stdin and stdout, then replaces either stream when its corresponding flag names a file. That keeps conversion logic working with readers and writers rather than separate file-only paths. section 10.4


