実践編
CLIツールの構築はGoの強みのひとつです。単一のバイナリにコンパイルされ、クロスプラットフォームに対応しており、依存関係も一切ありません。クラウドネイティブ時代において、GoでCLIを作成することは標準となっています。
docker から kubectl、そして gh に至るまで、主流の CLI ツールのほぼすべてが Go 言語で記述されています。このレッスンでは、CLI 開発のコアスキルをすべて習得し、完全なパスワードマネージャーをビルドします。
1. 学習内容
flagパッケージは、コマンドライン引数を解析しますos.Args未処理の引数へのアクセスos.Stdin/os.Stdout標準ストリーム- コブラライブラリとサブコマンドパターン
- CLIとファイルI/Oの統合
- プロジェクト:パスワードマネージャー CLI(パスワードの生成・保存・取得・エクスポート)
2. 運用エンジニアの実話
(1) 課題:毎回手動でパスワードを生成しなければならないこと
ボブは運用エンジニアで、さまざまなサービス用に複雑なパスワードを頻繁に生成する必要があります:
「今日はPostgreSQL用のパスワードを生成し、明日はRedis用、明後日は管理者アカウント用。そのたびにパスワード生成サイトを開いて、1Passwordにコピー&ペーストしなければならない――1日に5回も。本当に面倒だ。それに、サイトごとに要件が異なるんだ。『12文字で特殊文字を含む』とか、『16文字で曖昧な文字を含まない』とか……」
ボブは、次のようなCLIツールを探しています:
# 欲しいもの 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
// passgen.go — 初版:基本パスワードの生成
package main
import (
"crypto/rand"
"flag"
"fmt"
"math/big"
)
var (
length = flag.Int("length", 12, "パスワードの長さ")
special = flag.Bool("special", false, "特殊文字を含む")
count = flag.Int("count", 1, "生成数")
)
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))
}
}
$ 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 buildを実行した後、そのファイルをLinux、Mac、またはWindowsのサーバーにコピーするだけで実行できます。これが、CLIアプリケーションにGoを使用する最大の利点です。
3. flag パッケージ:コマンドライン引数の解析
(1) 旗の基本
package main
import (
"flag"
"fmt"
)
func main() {
// 登録パラメータ(ポインタに戻る)
name := flag.String("name", "World", "挨拶する相手の名前")
age := flag.Int("age", 0, "年齢")
verbose := flag.Bool("verbose", false, "詳細な出力")
// 分析(読み込み前に必ず呼び出す必要がある)
flag.Parse()
if *verbose {
fmt.Printf("name=%s, age=%d\n", *name, *age)
}
fmt.Printf("Hello, %s!\n", *name)
}
$ 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 |
▶ サンプル:フラグ付きパラメータ + フラグなしパラメータ
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// カスタマイズ Usage 情報
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "使用方法: mytool [オプション] <ファイルのパス>\n\n")
fmt.Fprintf(os.Stderr, "オプション:\n")
flag.PrintDefaults()
}
// 登録パラメータ
output := flag.String("o", "output.txt", "出力ファイルのパス")
limit := flag.Int("n", 0, "処理行数(0=すべて)")
debug := flag.Bool("debug", false, "デバッグモードを有効にする")
// 分析
flag.Parse()
// 非フラグ引数へのアクセス(残りのパラメータ)
args := flag.Args()
if len(args) < 1 {
flag.Usage()
os.Exit(1)
}
filePath := args[0]
fmt.Printf("ファイルの処理: %s\n", filePath)
fmt.Printf("出力: %s\n", *output)
fmt.Printf("行数: %d\n", *limit)
fmt.Printf("デバッグ: %v\n", *debug)
// 支持 -o と --o 2つの形式
// 支持 -o=file と -o file 2つの書き方
}
$ go run main.go -o result.txt -n 100 data.csv
ファイルの処理: data.csv
出力: result.txt
行数: 100
デバッグ: false
4. os.Args および os.Stdin/Stdout
(1) os.Args: 生の引数
package main
import (
"fmt"
"os"
)
func main() {
fmt.Printf("プログラム名: %s\n", os.Args[0])
fmt.Printf("パラメータ数: %d\n", len(os.Args)-1)
for i, arg := range os.Args[1:] {
fmt.Printf("パラメータ %d: %s\n", i+1, arg)
}
}
$ go run main.go hello world --debug
プログラム名: /tmp/main
パラメータ数: 3
パラメータ 1: hello
パラメータ 2: world
パラメータ 3: --debug
▶ サンプル:flag 変数(IntVar / StringVar)のバインディング
package main
import (
"flag"
"fmt"
)
func main() {
// flag.Int ポインタに戻る vs flag.IntVar 既存の変数にバインドする
var (
name string
port int
debug bool
)
flag.StringVar(&name, "name", "World", "氏名")
flag.IntVar(&port, "port", 8080, "ポート")
flag.BoolVar(&debug, "debug", false, "デバッグモード")
flag.Parse()
fmt.Printf("name=%s, port=%d, debug=%v\n", name, port, debug)
}
flag.IntVar(&dest, ...) は既存の変数にバインドされますが、flag.Int(...) はポインタを返します。両者は完全に同等ですので、好みのスタイルを選んでください。IntVar は、一箇所で変数を宣言する場合に適しています。
(2) os.Stdin: パイプ入力
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
stat, _ := os.Stdin.Stat()
hasStdin := (stat.Mode() & os.ModeCharDevice) == 0
if hasStdin {
// パイプから読み込む(echo "hello" | go run main.go)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := strings.ToUpper(scanner.Text())
fmt.Println(line)
}
} else {
// パラメータからの読み取り(go run main.go hello)
if len(os.Args) > 1 {
fmt.Println(strings.ToUpper(strings.Join(os.Args[1:], " ")))
} else {
fmt.Println("入力してください(パイプラインまたはパラメータ)")
}
}
}
$ echo "hello world" | go run main.go
HELLO WORLD
$ go run main.go hello world
HELLO WORLD
▶ サンプル:対話型入力 (fmt.Scanf)
package main
import "fmt"
func main() {
var name string
var age int
fmt.Print("お名前を入力してください: ")
fmt.Scanf("%s", &name)
fmt.Print("年齢を入力してください: ")
fmt.Scanf("%d", &age)
fmt.Printf("こんにちは %s,あなた %d 歳\n", name, age)
}
▶ サンプル:os.Stdout のリダイレクト
package main
import (
"fmt"
"os"
)
func main() {
// 直接書き込み stdout
fmt.Fprintln(os.Stdout, "標準出力")
fmt.Fprintln(os.Stderr, "エラー出力")
// ファイルへのリダイレクト
file, _ := os.Create("output.log")
defer file.Close()
// fmt.Fprintln 何でも書き込むことができます io.Writer
fmt.Fprintln(file, "これがファイルに書き込まれました")
fmt.Fprintln(file, "2行目")
}
(3) 入力ソースの比較
| 入力方式 | コマンド例 | 使用例 |
|---|---|---|
| コマンドライン引数 | mytool --name Alice |
設定オプション、ファイル名 |
| stdin パイプ | cat data | mytool |
大量のデータ、連鎖処理 |
| 対話型入力 | mytool はユーザーからの入力を待機しています |
ガイダンスが必要な入力 |
| 環境変数 | MYTOOL_DEBUG=1 mytool |
機密設定 |
5. cobra ライブラリ:サブコマンドモード
(1) コブラの概要
cobraは、Go言語で最も人気のあるCLIフレームワーク(Kubernetesの開発者が作成)であり、サブコマンド、ヘルプメッセージ、オートコンプリートをサポートしています。
# インストール cobra-cli
$ go install github.com/spf13/cobra-cli@latest
# プロジェクトの初期化
$ cobra-cli init passgen
$ cobra-cli add generate
$ cobra-cli add save
$ cobra-cli add list
▶ サンプル:「cobra」サブコマンドの構造
// cmd/root.go
パッケージ cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "passgen",
Short: "パスワードマネージャー",
Long: `生成、パスワードの保存と管理 CLI ツール。`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// 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: "ランダムなパスワードを生成する",
Long: `指定された長さと複雑さを持つランダムなパスワードを生成する。`,
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, "パスワードの長さ")
generateCmd.Flags().BoolVarP(&genSpecial, "special", "s", false, "特殊文字を含む")
generateCmd.Flags().IntVarP(&genCount, "count", "c", 1, "生成数")
}
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)
}
// main.go
package main
import "passgen/cmd"
func main() {
cmd.Execute()
}
$ go build -o passgen
$ ./passgen generate --length 16 --special
Kd9#mP2$xL7qR!vB
$ ./passgen generate -l 8
aB3xK9mP
(2) フラッグ対コブラ
| ディメンション | フラグパッケージ | コブラ |
|---|---|---|
| 組み込み | ✅ 標準ライブラリ | ❌ サードパーティ製 |
| サブコマンド | ❌ 未対応 | ✅ ネイティブ対応 |
| 自動車のトラブルシューティング | ✅ 基礎知識 | ✅ 外観 |
| オートコンプリート | ❌ | ✅ bash/zsh/fish |
| ユースケース | 簡単なスクリプト | 複雑なマルチコマンドツール |
6. 完全な例:パスワードマネージャーのCLI
これらすべての知識を統合して、包括的なパスワードマネージャーを作りましょう:
// main.go
パッケージ main
import (
"bufio"
"crypto/rand"
"encoding/json"
"flag"
"fmt"
"math/big"
"os"
"strings"
"time"
)
// ---------- データ構造 ----------
type PasswordEntry struct {
Name 文字列 `json:"name"`
Password 文字列 `json:"password"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type PasswordStore struct {
entries map[文字列]PasswordEntry
filePath 文字列
}
const defaultStoreFile = "passwords.json"
// ---------- ストレージ層 ----------
func NewPasswordStore(filePath 文字列) *PasswordStore {
store := &PasswordStore{
entries: make(map[文字列]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() エラー {
data, err := json.MarshalIndent(s.entries, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.filePath, data, 0600)
}
func (s *PasswordStore) Add(name, password 文字列) {
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 文字列) (PasswordEntry, bool) {
entry, ok := s.entries[name]
return entry, ok
}
func (s *PasswordStore) Delete(name 文字列) bool {
_, ok := s.entries[name]
if ok {
削除(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
}
// ---------- パスワードの生成 ----------
const (
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
digits = "0123456789"
specials = "!@#$%^&*()-_=+[]{}|;:,.<>?"
noAmbigu = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"
)
func generatePassword(length int, useSpecial bool) (文字列, エラー) {
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 文字列(pwd), nil
}
// ---------- CLI コマンド ----------
func cmdGenerate(args []文字列) {
cmd := flag.NewFlagSet("generate", flag.ExitOnError)
length := cmd.Int("length", 12, "パスワードの長さ")
special := cmd.Bool("special", false, "特殊文字を含む")
count := cmd.Int("count", 1, "生成数")
noAmbiguous := cmd.Bool("no-ambiguous", false, "混同されやすい文字を除外する(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 []文字列, store *PasswordStore) {
cmd := flag.NewFlagSet("save", flag.ExitOnError)
password := cmd.String("password", "", "パスワード(空欄のままにすると自動的に生成されます)")
length := cmd.Int("length", 16, "自動生成時のパスワードの長さ")
cmd.Parse(args)
name := cmd.Arg(0)
if name == "" {
fmt.Fprintln(os.Stderr, "使用方法: passgen save <name> [--password <pwd>]")
os.Exit(1)
}
pwd := *password
if pwd == "" {
var err エラー
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("✅ 保存済み: %s = %s\n", name, pwd)
}
func cmdGet(args []文字列, store *PasswordStore) {
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "使用方法: passgen get <name>")
os.Exit(1)
}
entry, ok := store.Get(args[0])
if !ok {
fmt.Fprintf(os.Stderr, "❌ 見つかりませんでした: %s\n", args[0])
os.Exit(1)
}
fmt.Printf("%s: %s\n", entry.Name, entry.Password)
}
func cmdList(args []文字列, store *PasswordStore) {
entries := store.List()
if len(entries) == 0 {
fmt.Println("(保存済みのパスワードはありません)")
return
}
fmt.Printf("%-20s %-20s %s\n", "名称", "パスワード", "作成日時")
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 []文字列, store *PasswordStore) {
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "使用方法: passgen 削除 <name>")
os.Exit(1)
}
if store.Delete(args[0]) {
fmt.Printf("🗑️ 削除済み: %s\n", args[0])
} else {
fmt.Fprintf(os.Stderr, "❌ 見つかりませんでした: %s\n", args[0])
}
}
func cmdExport(args []文字列, 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("📤 にエクスポート済み: %s\n", output)
}
// ---------- メイン関数 ----------
func printUsage() {
fmt.Println(`パスワードマネージャー CLI
使用方法:
passgen <command> [オプション]
コマンド:
generate ランダムなパスワードを生成する
save パスワードを保存する
get パスワードを取得する
list すべてのパスワードを表示する
削除 パスワードを削除する
export パスワードをファイルにエクスポートする
オプション:
--store ファイルの保存先を指定する(デフォルト: passwords.json)
例:
passgen generate --length 16 --special
passgen save mydb --length 20
passgen get mydb
passgen list
passgen 削除 mydb`)
}
func main() {
// 全体 --store パラメータ
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]
// 削除 store パラメータ
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 "削除":
cmdDelete(args, store)
case "export":
cmdExport(args, store)
case "help", "--help", "-h":
printUsage()
default:
fmt.Fprintf(os.Stderr, "不明なコマンド: %s\n", command)
fmt.Fprintf(os.Stderr, "実行 'passgen help' 利用可能なコマンドを表示する\n")
os.Exit(1)
}
}
使用目的:
# パスワードを生成する
$ go run main.go generate --length 16 --special
Kd9#mP2$xL7qR!vB
# パスワードを保存する(自動生成)
$ go run main.go save mydb --length 20
✅ 保存済み: mydb = aB3xK9mP$L7qR!vD2nFg
# パスワードを保存する(手動で指定する)
$ go run main.go save admin --password "MyStr0ng!Pass"
✅ 保存済み: admin = MyStr0ng!Pass
# パスワードを取得する
$ go run main.go get mydb
mydb: aB3xK9mP$L7qR!vD2nFg
# 一覧表示
$ go run main.go list
名称 パスワード 作成日時
------------------------------------------------------------
mydb aB3xK9mP$L7qR!v... 2026-07-08
admin MyStr0ng!Pass 2026-07-08
# エクスポート
$ go run main.go export
📤 にエクスポート済み: passwords_export.json
# 保存先を指定する
$ go run main.go --store vault.json generate -l 10
flowchart TD
A[passgen <command>] --> B{switch command}
B -->|generate| C[flag.NewFlagSet 分析<br/>--length --special --count]
B -->|save| D[分析 name + --password<br/>パスワードの自動生成または指定]
B -->|get| E[クリック name から map 検索]
B -->|list| F[走査 map 表を出力する]
B -->|delete| G[delete(map, name)]
B -->|export| H[json.Marshal + WriteFile]
C --> I[rand.Int 安全なランダム]
D --> J[store.Add → json → save]
E --> K[store.Get → 出力]
F --> L[store.List → フォーマット]
G --> M[store.Delete → json → save]
H --> N[store.List → json.Marshal]
flag.NewFlagSet はサブコマンドに使用され、各サブコマンドには独自のフラグセットがあります。flag.FlagSet.Parse は自身の引数のみを解析し、os.Args を変更することはありません。メインコマンドは、os.Args を使用して手動で値を割り当てる必要があります。
❓ よくある質問
flag と cobra のどちらを選べばいいですか?flag;複数コマンドのツール(gitスタイル:tool <command> [options])→ cobra。flagは標準ライブラリの一部ですが、cobraはより多くの機能を提供する一方で、依存関係の追加が必要となります。os.Args[1] を使用してコマンドを識別し、flag.NewFlagSet("sub", ...) を使用してサブコマンドの引数を解析します。Cobra での方法:rootCmd.AddCommand(subCmd) を使用すると、フレームワークが自動的に処理します。os.Args をどのように反復処理すればよいですか?os.Args[0] はプログラムのパス、os.Args[1:] は引数です。これらを反復処理するには、os.Args[i] または range os.Args[1:] を使用します。~/.config/passgen.json) への保存、(2) --store パラメータを使用したパスの指定、(3) 環境変数(PASSGEN_STORE など)。go build -o mytoolでコンパイルします;(2) GitHub Releasesにアップロードします;(3) go install github.com/user/mytool@latestを使用してインストールします。Goの静的バイナリを使えば、公開作業が非常に簡単になります。math/rand の代わりに crypto/rand を使用してください。—crypto/rand は、オペレーティングシステムの CSPRNG(/dev/urandom など)を使用しており、その出力は予測不可能です。math/rand は擬似乱数であり、暗号用途には適していません。bufio.NewScanner(os.Stdin) を使用すると行単位で読み取ることができ、fmt.Scanf を使用するとフォーマットされた入力を読み取ることができます。本番環境向けのツールには、gopass ライブラリ(パスワードをエコーしない)の使用をお勧めします。mytool 2>/dev/null がサイレントモードで実行されるように、エラーメッセージは os.Stderr (os.Stdout ではなく)に出力する必要があります。fmt.Fprintln(os.Stderr, "error") を使用してください。📖 まとめ
flagパッケージは、コマンドライン引数を解析します:flag.String/flag.Int/flag.Boolos.Argsは生の引数にアクセスできるため、柔軟な手動での解析が可能になりますos.Stdin/os.Stdout/os.Stderr標準 I/O ストリームflag.NewFlagSetは、サブコマンド形式の CLI を実装しています- cobra ライブラリは、サブコマンドのための包括的なフレームワークを提供しています
crypto/randは、暗号学的に安全な乱数を生成します- CLI ツールのデータ永続化:JSON ファイル + メモリ内マップキャッシュ
go buildは、クロスプラットフォームでの配布用に単一のバイナリにコンパイルされます
📝 練習問題
-
基本問題(難易度 ⭐):
flagパッケージを使用して、wcのクローンを作成してください。mywcは、--lines、--words、--charsの 3 つのフラグをサポートし、標準入力またはファイルから入力を読み取り、統計情報を出力します。 -
上級演習(難易度 ⭐⭐):このレッスンで取り上げるパスワードマネージャーに、
--store環境変数のサポートを追加してください。PASSGEN_STORE環境変数が設定されている場合は、まずそれを保存パスとして使用し、設定されていない場合は--storeパラメータを使用し、最後にデフォルトのパスを使用するようにしてください。 -
課題(難易度:⭐⭐⭐):パスワードマネージャーに
statsコマンドを追加する。パスワードの総数、最長および最短のパスワード、最も頻繁に使用されている文字、および作成日時(今日/今週/今月/それ以前)ごとに分類した統計情報を集計する。sort.Slice+strings.Count+time.Timeを必ず使用する必要があります。



