Go: Goの実践:CLIツールの開発 — フラグの解析、Cobraのサブコマンド、パスワードマネージャー

最終更新:2026-08-26

CLIツールの構築はGoの強みのひとつです。単一のバイナリにコンパイルされ、クロスプラットフォームに対応しており、依存関係も一切ありません。GoでCLIを作成することは、クラウドネイティブ時代において標準となっています。

docker から kubectl、そして gh に至るまで、主流の CLI ツールのほぼすべてが Go 言語で記述されています。このレッスンでは、CLI 開発のコアスキルをすべて習得し、完全なパスワードマネージャーを構築します。

1. 学習内容



2. 運用エンジニアの実話

(1) 課題:毎回手動でパスワードを生成しなければならないこと

ボブは運用エンジニアで、さまざまなサービス用に複雑なパスワードを頻繁に生成する必要があります:

「今日はPostgreSQL用のパスワードを生成し、明日はRedis用、明後日は管理者アカウント用。そのたびにパスワード生成サイトを開いて、1Passwordにコピー&ペーストしなければならない――1日に5回も。本当に面倒だ。それに、サイトごとに要件が異なるんだ。『12文字で特殊文字を含む』とか、『16文字で曖昧な文字を含まない』とか……」

ボブは、次のようなCLIツールを探しています:

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

しかし、彼のニーズを完全に満たすツールは市場には存在しなかった。そこで彼は、Goを使って自分でツールを作成することにした。

(2) Goの解決策:flagパッケージ + ファイルI/O

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) パフォーマンス:マニュアル操作とCLIの比較

手法 速度 再現性 CI/CD との連携 セキュリティ
ウェブサイトの生成 1回あたり30秒 ❌ (プライバシーリスク)
手動で作成 インスタンスあたり60秒 ✅ 複製可能
Go CLI 1操作あたり0.5秒 ✅ スクリプト化可能 ✅ 統合可能 ✅ ローカルで実行可能
💡 ヒント: Goのバイナリファイルのサイズは約10 MB(ランタイムを除く)です。go buildを実行した後、そのファイルをLinux、Mac、またはWindowsのサーバーにコピーするだけで実行できます。これが、CLIアプリケーションにGoを使用する最大の利点です。



3. flag パッケージ:コマンドライン引数の解析

(1) 旗の基本

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) flag タイプのクイックリファレンス

関数
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

▶ サンプル:フラグ付きパラメータとフラグなしパラメータ

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
}
▶ 試してみよう
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 および os.Stdin/Stdout

(1) os.Args: 生の引数

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

▶ サンプル: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)
}
▶ 試してみよう
💡 ヒント: flag.IntVar(&dest, ...) は既存の変数にバインドされますが、flag.Int(...) はポインタを返します。両者は完全に同等ですので、好みのスタイルを選んでください。IntVar は、一箇所で変数を宣言する場合に適しています。

(3) os.Stdin: パイプ入力

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

▶ サンプル:対話型入力 (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)
}
▶ 試してみよう

▶ サンプル: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")
}
▶ 試してみよう

(6) 入力ソースの比較

入力方法 コマンド例 使用例
コマンドライン引数 mytool --name Alice 設定オプション、ファイル名
stdin パイプ cat data | mytool 大量のデータ、連鎖処理
対話型入力 mytool はユーザーからの入力を待機しています ガイダンスが必要な入力
環境変数 MYTOOL_DEBUG=1 mytool 機密設定


5. cobra ライブラリ:サブコマンドモード

(1) コブラの概要

cobraは、Go言語で最も人気のあるCLIフレームワーク(Kubernetesの開発者が作成)であり、サブコマンド、ヘルプメッセージ、オートコンプリートをサポートしています。

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

▶ サンプル:「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)
    }
}
▶ 試してみよう
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) フラッグ対コブラ

ディメンション フラグパッケージ コブラ
組み込み ✅ 標準ライブラリ ❌ サードパーティ製
サブコマンド ❌ 未対応 ✅ ネイティブ対応
自動車のトラブルシューティング ✅ 基礎知識 ✅ 外観
オートコンプリート ✅ bash/zsh/fish
ユースケース 簡単なスクリプト 複雑なマルチコマンドツール


6. 完全な例:パスワードマネージャーのCLI

これらすべての知識を統合して、包括的なパスワードマネージャーを作りましょう:

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 {
        delete(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, "number 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 delete <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
  delete      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 delete 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 "delete":
        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)
    }
}

使用目的:

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]
🔥 よくある間違い: flag.NewFlagSet はサブコマンドに使用され、各サブコマンドには独自のフラグセットがあります。flag.FlagSet.Parse は自身の引数のみを解析し、os.Args を変更することはありません。メインコマンドは、os.Args を使用して手動で値を割り当てる必要があります。


❓ よくある質問

Q flagcobra のどちらを選べばいいですか?
A 単純なスクリプト(コマンド1つ、パラメータが数個)→ flag;複数コマンドのツール(gitスタイル:tool <command> [options])→ cobraflagは標準ライブラリの一部ですが、cobraはより多くの機能を提供する一方で、依存関係の追加が必要となります。
Q サブコマンドはどのように解析すればよいですか?
A 手動での方法:os.Args[1] を使用してコマンドを識別し、flag.NewFlagSet("sub", ...) を使用してサブコマンドの引数を解析します。Cobra での方法:rootCmd.AddCommand(subCmd) を使用すると、フレームワークが自動的に処理します。
Q os.Args をどのように反復処理すればよいですか?
A os.Args[0] はプログラムのパス、os.Args[1:] は引数です。これらを反復処理するには、os.Args[i] または range os.Args[1:] を使用します。
Q CLIではファイルI/Oはどのように使用されますか?
A 3つのモードがあります:(1) 固定パス(~/.config/passgen.json)への保存、(2) --storeパラメータを使用したパスの指定、(3) 環境変数(PASSGEN_STOREなど)。
Q CLIをGitHubに公開するにはどうすればよいですか?
A (1) go build -o mytoolでコンパイルします;(2) GitHub Releasesにアップロードします;(3) go install github.com/user/mytool@latestを使用してインストールします。Goの静的バイナリを使えば、公開作業が非常に簡単になります。
Q パスワード生成ツールはどのようにセキュリティを確保しているのですか?
A math/rand の代わりに crypto/rand を使用してください。—crypto/rand は、オペレーティングシステムの CSPRNG(/dev/urandom など)を使用しており、予測不可能です。math/rand は擬似乱数であり、暗号用途には適していません。
Q CLIツールで簡単なユーザー入力を処理するにはどうすればよいですか?
A bufio.NewScanner(os.Stdin) を使用すると行単位で読み取ることができ、fmt.Scanf を使用するとフォーマットされた入力を読み取ることができます。本番環境向けのツールには、gopass ライブラリ(パスワードを表示しない)の使用をお勧めします。
Q CLI ではエラーメッセージをどのように扱うべきですか?
A mytool 2>/dev/null がサイレントモードで実行されるように、エラーメッセージは os.Stderros.Stdout ではなく)に出力する必要があります。fmt.Fprintln(os.Stderr, "error") を使用してください。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐)flag パッケージを使用して、wc のクローンを作成してください。mywc は、--lines--words--chars の 3 つのフラグをサポートし、標準入力またはファイルから入力を読み取り、統計情報を出力します。

  2. 上級演習(難易度 ⭐⭐):このレッスンで取り上げるパスワードマネージャーに、--store 環境変数のサポートを追加してください。PASSGEN_STORE 環境変数が設定されている場合は、まずそれを保存パスとして使用し、設定されていない場合は --store パラメータを使用し、最後にデフォルトのパスを使用するようにしてください。

  3. 課題(難易度:⭐⭐⭐):パスワードマネージャーに stats コマンドを追加する。パスワードの総数、最長および最短のパスワード、最も頻繁に使用されている文字、および作成日時(今日/今週/今月/それ以前)ごとに分類した統計情報を集計する。sort.Slice + strings.Count + time.Time を必ず使用する必要があります。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%