Go: Na prática: Desenvolvimento de ferramentas de CLI

Última atualização: 2026-08-26

A criação de ferramentas de linha de comando (CLI) é um dos pontos fortes do Go — elas são compiladas em um único binário, são multiplataforma e não têm nenhuma dependência. Escrever CLIs em Go tornou-se o padrão na era do cloud-native.

De docker a kubectl e gh, quase todas as principais ferramentas de CLI são escritas em Go. Nesta aula, você vai dominar todas as habilidades essenciais do desenvolvimento de CLI e criar um gerenciador de senhas completo.

1. Você aprenderá


2. Uma história real de um engenheiro de operações

(1) Problema: Ter que gerar manualmente uma senha todas as vezes

Bob é um engenheiro de operações que frequentemente precisa gerar senhas complexas para diversos serviços:

“Hoje eu gerar uma senha para o PostgreSQL, amanhã para o Redis e depois de amanhã para uma conta de administrador. Toda vez, tenho que abrir um site gerador de senhas, copiar e colar no 1Password — cinco vezes por dia. É muito chato. Além disso, sites diferentes têm requisitos diferentes: 12 caracteres com caracteres especiais, 16 caracteres sem caracteres ambíguos...”

Bob está procurando uma ferramenta de CLI como esta:

BASH
# Desired CLI
$ passgen generate --length 16 --special
Generated: Kd9#mP2$xL7qR!vB

$ passgen save mydb -p Kd9#mP2$xL7qR!vB
Saved: mydb

$ passgen list
mydb    (created: 2026-07-08)
admin   (created: 2026-07-05)
api-key (created: 2026-07-01)

$ passgen get mydb
mydb: Kd9#mP2$xL7qR!vB

Mas não havia nenhuma ferramenta no mercado que atendesse plenamente às suas necessidades. Por isso, ele decidiu criar uma ele mesmo usando Go.

(2) Solução em Go: pacote de sinalizadores + E/S de arquivos

GO
// passgen.go — Version 1: Basic password generation
package main

import (
    "crypto/rand"
    "flag"
    "fmt"
    "math/big"
)

var (
    length  = flag.Int("length", 12, "password length")
    special = flag.Bool("special", false, "include special characters")
    count   = flag.Int("count", 1, "number of passwords to generate")
)

const (
    lowerChars   = "abcdefghijklmnopqrstuvwxyz"
    upperChars   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digitChars   = "0123456789"
    specialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?"
)

func generatePassword(length int, includeSpecial bool) string {
    charset := lowerChars + upperChars + digitChars
    if includeSpecial {
        charset += specialChars
    }

    pwd := make([]byte, length)
    for i := range pwd {
        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd)
}

func main() {
    flag.Parse()

    for i := 0; i < *count; i++ {
        fmt.Println(generatePassword(*length, *special))
    }
}
BASH
$ go run passgen.go --length 16 --special
Kd9#mP2$xL7qR!vB

$ go run passgen.go --length 8
aB3xK9mP

(3) Desempenho: Modo manual x CLI

Método Velocidade Repetibilidade Integração com CI/CD Segurança
Geração de sites 30s/vez ❌ (Risco à privacidade)
Criar manualmente 60 segundos por instância ✅ Pode ser duplicado
Go CLI 0,5 s por operação ✅ Programável ✅ Integrável ✅ Executa-se localmente
💡 Dica: Um binário do Go tem aproximadamente 10 MB (sem o runtime) e, após executar o go build, basta copiá-lo para qualquer servidor Linux, Mac ou Windows para executá-lo. Essa é a principal vantagem de usar o Go para aplicativos de CLI.


3. Pacote flag: Análise de argumentos da linha de comando

(1) Noções básicas sobre bandeiras

GO
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Register parameters (returns pointers)
    name := flag.String("name", "World", "name to greet")
    age := flag.Int("age", 0, "age")
    verbose := flag.Bool("verbose", false, "verbose output")

    // Parse (must be called before reading values)
    flag.Parse()

    if *verbose {
        fmt.Printf("name=%s, age=%d\n", *name, *age)
    }
    fmt.Printf("Hello, %s!\n", *name)
}
BASH
$ go run main.go --name Alice --age 28 --verbose
name=Alice, age=28
Hello, Alice!

$ go run main.go
Hello, World!

(2) Referência rápida para o tipo flag

Função Tipo Exemplo
flag.String(name, default, usage) *string --name Alice
flag.Int(name, default, usage) *int --port 8080
flag.Float64(name, default, usage) *float64 --ratio 0.5
flag.Bool(name, default, usage) *bool --verbose
flag.Duration(name, default, usage) *time.Duration --timeout 5s

(3) ▶ Exemplo: parâmetro com sinalizador curto + parâmetro sem sinalizador

GO
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    // Custom Usage message
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage: mytool [options] <file path>\n\n")
        fmt.Fprintf(os.Stderr, "Options:\n")
        flag.PrintDefaults()
    }

    // Register parameters
    output := flag.String("o", "output.txt", "output file path")
    limit := flag.Int("n", 0, "lines to process (0=all)")
    debug := flag.Bool("debug", false, "enable debug mode")

    // Parse
    flag.Parse()

    // Access non-flag arguments (remaining args)
    args := flag.Args()
    if len(args) < 1 {
        flag.Usage()
        os.Exit(1)
    }

    filePath := args[0]
    fmt.Printf("Processing file: %s\n", filePath)
    fmt.Printf("Output: %s\n", *output)
    fmt.Printf("Lines: %d\n", *limit)
    fmt.Printf("Debug: %v\n", *debug)

    // Supports both -o and --o forms
    // Supports both -o=file and -o file syntax
}
▶ Experimente
BASH
$ go run main.go -o result.txt -n 100 data.csv
Processing file: data.csv
Output: result.txt
Lines: 100
Debug: false

4. os.Args e os.Stdin/Stdout

(1) os.Args: Argumentos em formato bruto

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Printf("Program: %s\n", os.Args[0])
    fmt.Printf("Arg count: %d\n", len(os.Args)-1)

    for i, arg := range os.Args[1:] {
        fmt.Printf("Arg %d: %s\n", i+1, arg)
    }
}
BASH
$ go run main.go hello world --debug
Program: /tmp/main
Arg count: 3
Arg 1: hello
Arg 2: world
Arg 3: --debug

(2) ▶ Exemplo: Atribuição da variável flag (IntVar / StringVar)

GO
package main

import (
    "flag"
    "fmt"
)

func main() {
    // flag.Int returns a pointer vs flag.IntVar binds to an existing variable
    var (
        name   string
        port   int
        debug  bool
    )

    flag.StringVar(&name, "name", "World", "name")
    flag.IntVar(&port, "port", 8080, "port")
    flag.BoolVar(&debug, "debug", false, "debug mode")

    flag.Parse()

    fmt.Printf("name=%s, port=%d, debug=%v\n", name, port, debug)
}
▶ Experimente
💡 Dica: flag.IntVar(&dest, ...) se vincula a uma variável existente, enquanto flag.Int(...) retorna um ponteiro. Ambos são totalmente equivalentes — escolha o estilo que preferir. IntVar é mais adequado para declarar variáveis em um local centralizado.

(3) os.Stdin: Entrada por pipe

GO
package main

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

func main() {
    stat, _ := os.Stdin.Stat()
    hasStdin := (stat.Mode() & os.ModeCharDevice) == 0

    if hasStdin {
        // Read from pipe (echo "hello" | go run main.go)
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            line := strings.ToUpper(scanner.Text())
            fmt.Println(line)
        }
    } else {
        // Read from arguments (go run main.go hello)
        if len(os.Args) > 1 {
            fmt.Println(strings.ToUpper(strings.Join(os.Args[1:], " ")))
        } else {
            fmt.Println("Please provide input (pipe or arguments)")
        }
    }
}
BASH
$ echo "hello world" | go run main.go
HELLO WORLD

$ go run main.go hello world
HELLO WORLD

(4) ▶ Exemplo: Entrada interativa (fmt.Scanf)

GO
package main

import "fmt"

func main() {
    var name string
    var age int

    fmt.Print("Enter name: ")
    fmt.Scanf("%s", &name)

    fmt.Print("Enter age: ")
    fmt.Scanf("%d", &age)

    fmt.Printf("Hello %s, you are %d years old\n", name, age)
}
▶ Experimente

(5) ▶ Exemplo: Redirecionando os.Stdout

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    // Write directly to stdout
    fmt.Fprintln(os.Stdout, "Standard output")
    fmt.Fprintln(os.Stderr, "Error output")

    // Redirect to file
    file, _ := os.Create("output.log")
    defer file.Close()

    // fmt.Fprintln can write to any io.Writer
    fmt.Fprintln(file, "This line was written to the file")
    fmt.Fprintln(file, "Second line")
}
▶ Experimente

(6) Comparação das fontes de entrada

Método de entrada Exemplo de comando Caso de uso
Argumentos da linha de comando mytool --name Alice Opções de configuração, nome do arquivo
canal stdin cat data | mytool Grandes volumes de dados, processamento em cadeia
Entrada interativa mytool aguarda a entrada do usuário Entrada que requer orientação
Variável de ambiente MYTOOL_DEBUG=1 mytool Configuração sensível

5. Biblioteca cobra: modo de subcomandos

(1) Introdução ao Cobra

O cobra é o framework de CLI mais popular para Go (criado pelo criador do Kubernetes) e oferece suporte a subcomandos, mensagens de ajuda e autocompletar.

BASH
# Install cobra-cli
$ go install github.com/spf13/cobra-cli@latest

# Initialize project
$ cobra-cli init passgen
$ cobra-cli add generate
$ cobra-cli add save
$ cobra-cli add list

(2) ▶ Exemplo: Estrutura do subcomando “cobra”

GO
// cmd/root.go
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "passgen",
    Short: "Password Manager",
    Long:  `A CLI tool for generating, storing, and managing passwords.`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
▶ Experimente
GO
// cmd/generate.go
package cmd

import (
    "fmt"
    "crypto/rand"
    "math/big"

    "github.com/spf13/cobra"
)

var (
    genLength  int
    genSpecial bool
    genCount   int
)

var generateCmd = &cobra.Command{
    Use:   "generate",
    Short: "Generate a random password",
    Long:  `Generate a random password with specified length and complexity.`,
    Run: func(cmd *cobra.Command, args []string) {
        for i := 0; i < genCount; i++ {
            fmt.Println(generatePassword(genLength, genSpecial))
        }
    },
}

func init() {
    rootCmd.AddCommand(generateCmd)
    generateCmd.Flags().IntVarP(&genLength, "length", "l", 12, "password length")
    generateCmd.Flags().BoolVarP(&genSpecial, "special", "s", false, "include special characters")
    generateCmd.Flags().IntVarP(&genCount, "count", "c", 1, "number to generate")
}

func generatePassword(length int, special bool) string {
    charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    if special {
        charset += "!@#$%^&*()-_=+[]{}|;:,.<>?"
    }
    pwd := make([]byte, length)
    for i := range pwd {
        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd)
}
GO
// main.go
package main

import "passgen/cmd"

func main() {
    cmd.Execute()
}
BASH
$ go build -o passgen
$ ./passgen generate --length 16 --special
Kd9#mP2$xL7qR!vB
$ ./passgen generate -l 8
aB3xK9mP

(3) Flag x Cobra

Dimensão pacote de bandeiras cobra
Integrado ✅ Biblioteca padrão ❌ De terceiros
Subcomando ❌ Não compatível ✅ Compatível nativamente
Ajuda sobre carros ✅ Noções básicas ✅ Estética
Autocompletar ✅ bash/zsh/fish
Casos de uso Scripts simples Ferramentas complexas com vários comandos

6. Exemplo completo: CLI do Password Manager

Reúna todo esse conhecimento em um gerenciador de senhas completo:

GO
// main.go
package main

import (
    "bufio"
    "crypto/rand"
    "encoding/json"
    "flag"
    "fmt"
    "math/big"
    "os"
    "strings"
    "time"
)

// ---------- Data Structures ----------

type PasswordEntry struct {
    Name      string    `json:"name"`
    Password  string    `json:"password"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}

type PasswordStore struct {
    entries  map[string]PasswordEntry
    filePath string
}

const defaultStoreFile = "passwords.json"

// ---------- Storage Layer ----------

func NewPasswordStore(filePath string) *PasswordStore {
    store := &PasswordStore{
        entries:  make(map[string]PasswordEntry),
        filePath: filePath,
    }
    store.load()
    return store
}

func (s *PasswordStore) load() {
    data, err := os.ReadFile(s.filePath)
    if err != nil {
        return
    }
    json.Unmarshal(data, &s.entries)
}

func (s *PasswordStore) save() error {
    data, err := json.MarshalIndent(s.entries, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(s.filePath, data, 0600)
}

func (s *PasswordStore) Add(name, password string) {
    now := time.Now()
    if existing, ok := s.entries[name]; ok {
        existing.Password = password
        existing.UpdatedAt = now
        s.entries[name] = existing
    } else {
        s.entries[name] = PasswordEntry{
            Name:      name,
            Password:  password,
            CreatedAt: now,
        }
    }
    s.save()
}

func (s *PasswordStore) Get(name string) (PasswordEntry, bool) {
    entry, ok := s.entries[name]
    return entry, ok
}

func (s *PasswordStore) Delete(name string) bool {
    _, ok := s.entries[name]
    if ok {
        Excluir(s.entries, name)
        s.save()
    }
    return ok
}

func (s *PasswordStore) List() []PasswordEntry {
    result := make([]PasswordEntry, 0, len(s.entries))
    for _, entry := range s.entries {
        result = append(result, entry)
    }
    return result
}

// ---------- Password Generation ----------

const (
    letters  = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digits   = "0123456789"
    specials = "!@#$%^&*()-_=+[]{}|;:,.<>?"
    noAmbigu = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"
)

func generatePassword(length int, useSpecial bool) (string, error) {
    if length < 4 {
        return "", fmt.Errorf("password length must be at least 4")
    }

    charset := letters + digits
    if useSpecial {
        charset += specials
    }

    pwd := make([]byte, length)
    for i := range pwd {
        n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        if err != nil {
            return "", err
        }
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd), nil
}

// ---------- CLI Commands ----------

func cmdGenerate(args []string) {
    cmd := flag.NewFlagSet("generate", flag.ExitOnError)
    length := cmd.Int("length", 12, "password length")
    special := cmd.Bool("special", false, "include special characters")
    count := cmd.Int("count", 1, "Valor to generate")
    _ = cmd.Bool("no-ambiguous", false, "exclude ambiguous characters (0OIl1)")
    cmd.Parse(args)

    for i := 0; i < *count; i++ {
        pwd, err := generatePassword(*length, *special)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
        fmt.Println(pwd)
    }
}

func cmdSave(args []string, store *PasswordStore) {
    cmd := flag.NewFlagSet("save", flag.ExitOnError)
    password := cmd.String("password", "", "password (leave empty to auto-generate)")
    length := cmd.Int("length", 16, "auto-generated password length")
    cmd.Parse(args)

    name := cmd.Arg(0)
    if name == "" {
        fmt.Fprintln(os.Stderr, "Usage: passgen save <name> [--password <pwd>]")
        os.Exit(1)
    }

    pwd := *password
    if pwd == "" {
        var err error
        pwd, err = generatePassword(*length, true)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
    }

    store.Add(name, pwd)
    fmt.Printf("Saved: %s = %s\n", name, pwd)
}

func cmdGet(args []string, store *PasswordStore) {
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "Usage: passgen get <name>")
        os.Exit(1)
    }

    entry, ok := store.Get(args[0])
    if !ok {
        fmt.Fprintf(os.Stderr, "Not found: %s\n", args[0])
        os.Exit(1)
    }
    fmt.Printf("%s: %s\n", entry.Name, entry.Password)
}

func cmdList(args []string, store *PasswordStore) {
    entries := store.List()
    if len(entries) == 0 {
        fmt.Println("(no saved passwords)")
        return
    }

    fmt.Printf("%-20s %-20s %s\n", "Name", "Password", "Created")
    fmt.Println(strings.Repeat("-", 60))
    for _, e := range entries {
        pwd := e.Password
        if len(pwd) > 16 {
            pwd = pwd[:16] + "..."
        }
        fmt.Printf("%-20s %-20s %s\n",
            e.Name, pwd, e.CreatedAt.Format("2006-01-02"))
    }
}

func cmdDelete(args []string, store *PasswordStore) {
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "Usage: passgen Excluir <name>")
        os.Exit(1)
    }

    if store.Delete(args[0]) {
        fmt.Printf("Deleted: %s\n", args[0])
    } else {
        fmt.Fprintf(os.Stderr, "Not found: %s\n", args[0])
    }
}

func cmdExport(args []string, store *PasswordStore) {
    output := "passwords_export.json"
    if len(args) > 0 {
        output = args[0]
    }

    data, _ := json.MarshalIndent(store.List(), "", "  ")
    os.WriteFile(output, data, 0600)
    fmt.Printf("Exported to: %s\n", output)
}

// ---------- Main Function ----------

func printUsage() {
    fmt.Println(`Password Manager CLI

Usage:
  passgen <command> [options]

Commands:
  generate    Generate a random password
  save        Save a password
  get         Retrieve a password
  list        List all passwords
  Excluir      Delete a password
  export      Export passwords to a file

Options:
  --store     Specify storage file path (default: passwords.json)

Examples:
  passgen generate --length 16 --special
  passgen save mydb --length 20
  passgen get mydb
  passgen list
  passgen Excluir mydb`)
}

func main() {
    // Global --store parameter
    storeFile := defaultStoreFile
    if len(os.Args) > 1 {
        for i := 1; i < len(os.Args); i++ {
            if os.Args[i] == "--store" && i+1 < len(os.Args) {
                storeFile = os.Args[i+1]
                // Remove the store parameter
                os.Args = append(os.Args[:i], os.Args[i+2:]...)
                break
            }
        }
    }

    store := NewPasswordStore(storeFile)

    if len(os.Args) < 2 {
        printUsage()
        return
    }

    command := os.Args[1]
    args := os.Args[2:]

    switch command {
    case "generate":
        cmdGenerate(args)
    case "save":
        cmdSave(args, store)
    case "get":
        cmdGet(args, store)
    case "list":
        cmdList(args, store)
    case "Excluir":
        cmdDelete(args, store)
    case "export":
        cmdExport(args, store)
    case "help", "--help", "-h":
        printUsage()
    default:
        fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
        fmt.Fprintf(os.Stderr, "Run 'passgen help' to see available commands\n")
        os.Exit(1)
    }
}

Uso pretendido:

BASH
# Generate password
$ go run main.go generate --length 16 --special
Kd9#mP2$xL7qR!vB

# Save password (auto-generated)
$ go run main.go save mydb --length 20
Saved: mydb = aB3xK9mP$L7qR!vD2nFg

# Save password (manually specified)
$ go run main.go save admin --password "MyStr0ng!Pass"
Saved: admin = MyStr0ng!Pass

# Retrieve password
$ go run main.go get mydb
mydb: aB3xK9mP$L7qR!vD2nFg

# List
$ go run main.go list
Name                 Password              Created
------------------------------------------------------------
mydb                 aB3xK9mP$L7qR!v...   2026-07-08
admin                MyStr0ng!Pass         2026-07-08

# Export
$ go run main.go export
Exported to: passwords_export.json

# Specify storage file
$ go run main.go --store vault.json generate -l 10
100%
flowchart TD
    A[passgen command] --> B{switch command}
    B -->|generate| C[flag.NewFlagSet parse<br/>--length --special --count]
    B -->|save| D[Parse name + --password<br/>Auto-generate or specify password]
    B -->|get| E[Look up by name from map]
    B -->|list| F[Iterate map, output table]
    B -->|delete| G[delete(map, name)]
    B -->|export| H[json.Marshal + WriteFile]
    C --> I[rand.Int secure random]
    D --> J[store.Add → json → save]
    E --> K[store.Get → output]
    F --> L[store.List → format]
    G --> M[store.Delete → json → save]
    H --> N[store.List → json.Marshal]
🔥 Erro comum: flag.NewFlagSet é usado para subcomandos, e cada subcomando possui seu próprio conjunto de sinalizadores. flag.FlagSet.Parse analisa apenas seus próprios argumentos e não modifica os.Args. O comando principal deve atribuir valores manualmente usando os.Args.


❓ Perguntas Frequentes

P: Como faço para escolher entre flag e cobra? R: Scripts simples (1 comando, alguns parâmetros) → flag; ferramentas com vários comandos (estilo git: tool <command> [options]) → cobra. O flag faz parte da biblioteca padrão, enquanto o cobra oferece mais recursos, mas requer a adição de uma dependência.

P: Como faço para analisar subcomandos? R: Método manual: use os.Args[1] para identificar o comando e flag.NewFlagSet("sub", ...) para analisar os argumentos do subcomando. Método Cobra: use rootCmd.AddCommand(subCmd), e a estrutura cuida disso automaticamente.

P: Como faço para percorrer os.Args? R: os.Args[0] é o caminho do programa, e os.Args[1:] são os argumentos. Você pode percorrê-los usando os.Args[i] ou range os.Args[1:].

P: Como a E/S de arquivos é utilizada na CLI? R: Existem três modos: (1) Salvar em um caminho fixo (~/.config/passgen.json); (2) Especificar um caminho usando o parâmetro --store; (3) Variáveis de ambiente (como PASSGEN_STORE).

P: Como faço para publicar uma CLI no GitHub? R: (1) Compile com go build -o mytool; (2) Envie para o GitHub Releases; (3) Instale usando go install github.com/user/mytool@latest. Os binários estáticos do Go tornam a publicação extremamente simples.

P: Como o gerador de senhas garante a segurança? R: Use crypto/rand em vez de math/randcrypto/rand utiliza o CSPRNG do sistema operacional (como /dev/urandom), que é imprevisível. math/rand é pseudoaleatório e não é adequado para fins criptográficos.

P: Como uma ferramenta de CLI pode lidar com entradas simples do usuário? R: Use bufio.NewScanner(os.Stdin) para ler linha por linha ou fmt.Scanf para ler entradas formatadas. Para ferramentas de produção, recomendamos a biblioteca gopass (que não exibe senhas na tela).

P: Como as mensagens de erro devem ser tratadas na CLI? R: As mensagens de erro devem ser gravadas em os.Stderr (em vez de os.Stdout) para que mytool 2>/dev/null seja executado silenciosamente. Use fmt.Fprintln(os.Stderr, "error").


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Use o pacote flag para escrever um clone do wc: o mywc suporta três opções — --lines, --words e --chars —, lê a entrada do stdin ou de um arquivo e exibe as estatísticas.

  2. Exercício avançado (Dificuldade ⭐⭐): Adicione suporte à variável de ambiente --store ao gerenciador de senhas nesta lição: se a variável de ambiente PASSGEN_STORE estiver definida, use-a como caminho de armazenamento em primeiro lugar; caso contrário, use o parâmetro --store; e, por fim, use o caminho padrão.

  3. Desafio (Dificuldade: ⭐⭐⭐): Adicione um comando stats ao gerenciador de senhas: conte o número total de senhas, a senha mais longa e a mais curta, os caracteres mais usados e as estatísticas agrupadas por data de criação (hoje/esta semana/este mês/antes). Você deve usar sort.Slice + strings.Count + time.Time.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%