fzf/pecoでCLIをインタラクティブ化し、堅牢な自動化を実現するDevOpsプラクティス

Linux・CLI・DevOpsカテゴリを表すパンダのイラスト Linux・CLI・DevOps

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

fzf/pecoでCLIをインタラクティブ化し、堅牢な自動化を実現するDevOpsプラクティス

DevOps環境において、コマンドラインインターフェース(CLI)の操作性を向上させ、同時に堅牢な自動化を実現することは重要な課題です。本記事では、インタラクティブなフィルタリングツールであるfzfとpecoを活用し、安全なBashスクリプト、curl/jqによるAPI連携、systemdによるサービス管理を組み合わせることで、これらの課題を解決するDevOpsプラクティスを紹介します。

1. 要件と前提

1.1. 要件

  • CLI操作のインタラクティブ化: fzfまたはpecoを用いて、動的に生成される選択肢からユーザーが容易に選択できるようにする。

  • 堅牢なスクリプト: Bashスクリプトは冪等(idempotent)であり、エラー発生時に適切にクリーンアップされること。

  • 外部システム連携: curl を用いた安全なAPI呼び出し(TLS、再試行、バックオフ)と、jq を用いたJSON処理。

  • 自動化と管理: systemd unit/timer を用いて、定期的な処理を定義し、ログ管理を容易にする。

  • 権限分離: 可能な限り非rootユーザーで実行し、root権限が必要な場合は最小限に留める。

1.2. 前提

  • Linux環境(Systemdが利用可能であること)。

  • Bashがシェルとして利用可能であること。

  • curl, jq, fzf, peco がインストールされていること。

2. 実装

2.1. fzf/pecoのインストールと安全なBashスクリプトのテンプレート

まず、fzfとpecoをインストールします。ここでは、GitHub Releasesからバイナリをダウンロードし、PATH の通ったディレクトリに配置する冪等なスクリプト例を示します。これにより、パッケージマネージャーの差異に依存せず、特定のバージョンを管理しやすくなります。

#!/usr/bin/env bash

# File: install_tools.sh

# 厳格なエラーハンドリングを有効化
set -euo pipefail

# スクリプト終了時のクリーンアップ処理
trap cleanup EXIT
cleanup() {
    if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
        rm -rf "$TMP_DIR"
        echo "一時ディレクトリ $TMP_DIR を削除しました。" >&2
    fi
}

# 一時ディレクトリの作成
TMP_DIR=$(mktemp -d -t install_tools_XXXXXX)
echo "一時ディレクトリを作成しました: $TMP_DIR" >&2

# インストール先のディレクトリ。PATHが通っている場所を指定
INSTALL_DIR="/usr/local/bin" # root権限が必要

# root権限の確認
if [[ "$EUID" -ne 0 && "$INSTALL_DIR" == "/usr/local/bin" ]]; then
    echo "警告: /usr/local/bin へのインストールにはroot権限が必要です。" >&2
    echo "一般ユーザーとして実行する場合は INSTALL_DIR を変更してください (例: \$HOME/.local/bin)。" >&2
    exit 1
fi

# fzfのインストール (最新版を取得) [1]
install_fzf() {
    local FZF_VERSION="0.50.0"
    local FZF_URL="https://github.com/junegunn/fzf/releases/download/${FZF_VERSION}/fzf-${FZF_VERSION}-linux_amd64.tar.gz"
    local FZF_BIN="fzf"

    echo "fzf ${FZF_VERSION} をインストール中..."
    if command -v "$FZF_BIN" &> /dev/null && "$FZF_BIN" --version | grep -q "$FZF_VERSION"; then
        echo "fzf ${FZF_VERSION} は既にインストールされています。スキップします。"
        return 0
    fi

    echo "fzfをダウンロード: $FZF_URL"
    curl -LsS "$FZF_URL" | tar -xz -C "$TMP_DIR"
    install -m 755 "$TMP_DIR/$FZF_BIN" "$INSTALL_DIR/$FZF_BIN"
    echo "fzfを $INSTALL_DIR/$FZF_BIN にインストールしました。"
}

# pecoのインストール (最新版を取得) [2]
install_peco() {
    local PECO_VERSION="v0.5.12"
    local PECO_URL="https://github.com/peco/peco/releases/download/${PECO_VERSION}/peco_linux_amd64.tar.gz"
    local PECO_BIN="peco"

    echo "peco ${PECO_VERSION} をインストール中..."
    if command -v "$PECO_BIN" &> /dev/null && "$PECO_BIN" --version | grep -q "$PECO_VERSION"; then
        echo "peco ${PECO_VERSION} は既にインストールされています。スキップします。"
        return 0
    fi

    echo "pecoをダウンロード: $PECO_URL"
    curl -LsS "$PECO_URL" | tar -xz -C "$TMP_DIR"
    install -m 755 "$TMP_DIR/$PECO_BIN" "$INSTALL_DIR/$PECO_BIN"
    echo "pecoを $INSTALL_DIR/$PECO_BIN にインストールしました。"
}

install_fzf
install_peco

echo "すべてのツールがインストールされました。"

2.2. fzf/pecoを用いたインタラクティブコマンドの作成例

ここでは、外部APIから取得したリソースリストをfzfで選択し、その詳細を表示するスクリプトを作成します。

#!/usr/bin/env bash

# File: interactive_api_selector.sh

set -euo pipefail
trap cleanup EXIT
cleanup() {
    if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
        rm -rf "$TMP_DIR"
        echo "一時ディレクトリ $TMP_DIR を削除しました。" >&2
    fi
}
TMP_DIR=$(mktemp -d -t api_selector_XXXXXX)
echo "一時ディレクトリを作成しました: $TMP_DIR" >&2

fetch_api_data() {
    local API_URL="https://jsonplaceholder.typicode.com/posts"
    local MAX_RETRIES=5
    local RETRY_DELAY_SEC=2

    local CURL_OPTS=(
        --silent --show-error --fail
        --retry "$MAX_RETRIES"
        --retry-delay "$RETRY_DELAY_SEC"
        --retry-max-time 60
        --connect-timeout 5
        --max-time 10
        --tlsv1.2
    )

    echo "APIからデータを取得中: $API_URL" >&2
    RESPONSE=$(curl "${CURL_OPTS[@]}" "$API_URL")

    if [[ $? -ne 0 ]]; then
        echo "エラー: APIからデータを取得できませんでした。" >&2
        exit 1
    fi

    if ! echo "$RESPONSE" | jq -e . &> /dev/null; then
        echo "エラー: API応答が有効なJSONではありません。" >&2
        exit 1
    fi

    echo "$RESPONSE"
}

echo "DevOpsリソースセレクター"
API_RESPONSE=$(fetch_api_data)

SELECTED_POST_LINE=$(echo "$API_RESPONSE" | jq -r '.[] | "\(.id): \(.title)"' | fzf --prompt="投稿を選択してください: ")

if [[ -z "$SELECTED_POST_LINE" ]]; then
    echo "選択がキャンセルされました。終了します。"
    exit 0
fi

SELECTED_ID=$(echo "$SELECTED_POST_LINE" | cut -d':' -f1 | tr -d '[:space:]')
if [[ -z "$SELECTED_ID" ]]; then
    echo "エラー: 選択された行からIDを抽出できませんでした。" >&2
    exit 1
fi

echo "--- 選択された投稿の詳細 ---"
echo "$API_RESPONSE" | jq --arg id "$SELECTED_ID" '.[] | select(.id == ($id | tonumber))'
echo "--------------------------"

echo "スクリプトが正常に完了しました。"

2.3. systemd unit/timerでの定期実行設定

このインタラクティブスクリプトは通常手動で実行しますが、ここでは、CLIの選択結果を基に定期的な処理をトリガーする「非インタラクティブな」部分を systemd で管理する例を示します。

2.3.1. スクリプト (my_automated_task.sh)

#!/usr/bin/env bash

# File: my_automated_task.sh

set -euo pipefail
trap cleanup EXIT
cleanup() {
    if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
        rm -rf "$TMP_DIR"
        echo "$(date -Iseconds) [INFO] Temporary directory $TMP_DIR removed." >&2
    fi
}
TMP_DIR=$(mktemp -d -t automated_task_XXXXXX)
echo "$(date -Iseconds) [INFO] Temporary directory created: $TMP_DIR" >&2

echo "$(date -Iseconds) [INFO] my_automated_task.sh が実行されました。"

REPORT_API_URL="https://jsonplaceholder.typicode.com/todos/1"
REPORT_DATA=$(curl -s --fail --retry 3 --retry-delay 5 --tlsv1.2 "$REPORT_API_URL")

if [[ $? -ne 0 ]]; then
    echo "$(date -Iseconds) [ERROR] レポートデータを取得できませんでした。" >&2
    exit 1
fi

REPORT_TITLE=$(echo "$REPORT_DATA" | jq -r '.title')
echo "$(date -Iseconds) [INFO] 本日のレポートタイトル: $REPORT_TITLE"

REPORT_FILE="$TMP_DIR/daily_report_$(date +%Y%m%d%H%M%S).txt"
echo "--- Daily Report for $(date) ---" > "$REPORT_FILE"
echo "Task Title: $REPORT_TITLE" >> "$REPORT_FILE"
echo "Raw Data: $REPORT_DATA" >> "$REPORT_FILE"
echo "--------------------------------" >> "$REPORT_FILE"

echo "$(date -Iseconds) [INFO] レポートが $REPORT_FILE に生成されました。"
exit 0

2.3.2. systemd Unitファイル (~/.config/systemd/user/my-automated-task.service)

[Unit]
Description=My Automated Daily Task
Documentation=https://github.com/your/repo
Requires=network-online.target
After=network-online.target

[Service]
ExecStart=/path/to/my_automated_task.sh
User=%i
WorkingDirectory=%h
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=default.target

2.3.3. systemd Timerファイル (~/.config/systemd/user/my-automated-task.timer)

[Unit]
Description=Run My Automated Daily Task every day
Requires=my-automated-task.service

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

2.3.4. systemdサービスの有効化と起動

  1. ユーザーサービスを有効にするための準備:

    loginctl enable-linger $USER
    
  2. systemdサービスとタイマーをリロード・有効化:

    systemctl --user daemon-reload
    systemctl --user enable my-automated-task.timer
    systemctl --user start my-automated-task.timer
    
  3. 状態確認:

    systemctl --user status my-automated-task.timer
    systemctl --user status my-automated-task.service
    
  4. ログ確認:

    journalctl --user -u my-automated-task.service -f
    

3. 検証

  • インタラクティブスクリプトの検証: interactive_api_selector.sh を手動で実行し、fzfまたはpecoが起動して選択肢が表示されること、選択後に詳細が出力されることを確認します。

  • 自動化スクリプトの検証: my_automated_task.sh を単独で実行し、期待通りのログと出力が生成されることを確認します。

  • systemdタイマーの検証: タイマーが適切に設定され、指定時刻にサービスが起動し、ログに処理結果が記録されることを確認します。

4. 運用

  • ログ監視: journalctl を用いて、定期実行されるサービスのログを定期的に確認し、エラーが発生していないか監視します。

  • スクリプトの更新: スクリプトに変更を加えた場合は、適切にバージョン管理し、systemctl --user daemon-reload を実行して変更を反映させます。

  • セキュリティ: スクリプトは最小限の権限で実行されるべきです。APIキーなどの機密情報は環境変数や秘密管理ツールを使用し、スクリプト内にハードコードしないことが重要です。

5. トラブルシュート

5.1. スクリプトが動作しない

  • set -euo pipefail により、予期せぬエラーでスクリプトが停止する場合があります。echo や set -x でデバッグ情報を出力します。

  • PATH が正しく設定されているか確認します。

5.2. systemdサービスが起動しない/動作がおかしい

  • ログの確認: journalctl --user -u my-automated-task.service でサービスログを確認します。

  • タイマーの状態確認: systemctl --user status my-automated-task.timer で状態を確認します。

6. まとめ

本記事では、fzf/pecoによるCLIのインタラクティブ化、堅牢なBashスクリプトの記述、curl/jqによる外部API連携、そしてsystemd unit/timerによる定期実行という一連のDevOpsプラクティスを紹介しました。

graph TD
    A["ユーザー"] -->|"インタラクティブ選択"| B(interactive_api_selector.sh)
    B -->|"API呼び出し (curl/jq)"| C("外部APIサービス")
    C -->|"JSONデータ応答"| B
    B -->|"結果表示/設定ファイル出力"| D("処理結果/設定ファイル")
    D -->|"定期実行をトリガー"| E(my-automated-task.timer)
    E -->|"指定時刻に起動"| F(my-automated-task.service)
    F -->|"自動処理スクリプト実行"| G(my_automated_task.sh)
    G -->|"API呼び出し (curl/jq)"| C
    C -->|"JSONデータ応答"| G
    G -->|"ログ出力/レポート生成"| H("journalctl/レポートファイル")
    H -->|"監視/確認"| A

このフローにより、ユーザーはインタラクティブな操作で柔軟にタスクを開始でき、その結果に基づいた後続の処理はsystemdによって堅牢かつ自動的に実行されます。最小権限の原則を守り、セキュアなコードプラクティスを適用することで、安定したDevOps環境を構築・運用できます。


[1] junegunn/fzf GitHub Releases, “fzf 0.50.0”, 2024-06-25, https://github.com/junegunn/fzf/releases/tag/0.50.0 [2] peco/peco GitHub Releases, “v0.5.12”, 2024-05-18, https://github.com/peco/peco/releases/tag/v0.5.12 [3] Red Symbol, “The Unofficial Bash Strict Mode”, 2010-06-03, http://redsymbol.net/articles/unofficial-bash-strict-mode/ [4] GNU Bash Manual, “Trap”, https://www.gnu.org/software/bash/manual/bash.html#index-trap [5] ArchWiki, “Systemd/Timers”, 2024-06-21, https://wiki.archlinux.org/title/Systemd/Timers [6] Red Hat, “Managing services with systemd”, 2021-08-04, https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_services_with_systemd/managing-services-using-systemctl_managing-services-with-systemd [7] curl.se, “curl man page”, https://curl.se/docs/manpage.html [8] jq, “jq Manual”, https://jqlang.github.io/jq/manual/

この記事の更新履歴

この記事は、生成AIを活用した自動レビュー・更新フローにより内容を見直し、必要な修正を反映しています。

2026年9月16日

  • 変更まとめセクションの冒頭にある不要な読点や不自然な文字を修正しました。

文書情報

記事タイトル
fzf/pecoでCLIをインタラクティブ化し、堅牢な自動化を実現するDevOpsプラクティス
作成日
更新日
Source URL
https://papanda925.com/?p=4684

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました