8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini CLIは“シェル芸”の代替となるか?ログファイル加工処理で試してみた

Last updated at Posted at 2025-07-28

はじめに

Gemini CLIがリリースされて、コマンドラインから簡単に生成AIを利用できるようになりました。
このおかげで、ローカルマシン上のファイル・ディレクトリ操作もAIに処理させることができます。
ということは、これまでサーバ上や仮想コンテナ上で運用されていた魔法のようなシェルスクリプト、いわゆるシェル芸も、生成AIに任せられる可能性があるのではないかと考え、実験を行いました。

要約と結果

以下、予想以上に長くなってしまったので、本記事の要約と結果を先にまとめます。

要約

  • ワンライナーで書けるシェルスクリプトをGemini CLIで再現できるかを実験
  • 対話モード、非対話モードそれぞれで動作を確認
  • プロンプトを詳細に書くことで、精度が向上するか検証

結果

  • 雑なプロンプトでも、そこそこの精度で生成AIがプロセスを提案してくれる
  • 処理結果のファイルは作ってくれることもあるし、作ってくれないこともあり、安定しない
  • ワンライナーで済む処理も、十数行のPythonコードとして実装しようとする傾向がある
  • 対話を重ねていくことで、精度が高くなっていく
  • プロンプトに制約としてシェルコマンドを使わせることで、期待通りの結果を得られることもあった

結論

  • 頑張ればシェル芸を生成AIに再現させることは可能
    • ただし、プロンプトをシェル芸以上に頑張って書かなければならない
  • 人はまだシェル芸を捨てられない

実行環境

項目 バージョン
OS macOS Sequoia 15.5
Gemini CLI 0.1.14
Model gemini-2.5-pro

準備

シェルとGemini CLIでファイル操作するため、まずはサンプルのログファイルを作ります。

ログファイル作成スクリプト

以下のPythonスクリプトを任意のディレクトリに格納して、python generate_log.py コマンドを実行します。(環境によっては python3 generate_log.py コマンドを使用してください)

100MB弱のサンプルログファイルを作るため、ストレージ使用量が気になる方は、プログラム内の定数 TARGET_FILE_SIZE_MB を適宜変更の上実行してください。

generate_log.py
import datetime
import random
import os

# --- 設定値 ---
OUTPUT_FILENAME = "sample_log.log"
TARGET_FILE_SIZE_MB = 100
AVERAGE_LINE_LENGTH = 120  # 推定される一行の長さ(バイト数)
TARGET_LINES = int(TARGET_FILE_SIZE_MB * 1024 * 1024 / AVERAGE_LINE_LENGTH)

START_DATETIME_STR = "2025-07-25 00:00:00.000"
TIME_INCREMENT_MS = 5      # 1行ごとの時間加算(ミリ秒)

# ログレベルと出現頻度 (INFOは残りの全て)
LOG_LEVELS = ["INFO", "WARN", "ERROR", "FATAL"]
WARN_RATIO = 5000    # 5,000行に1件WARN
ERROR_RATIO = 10000  # 10,000行に1件ERROR
FATAL_RATIO = 50000  # 50,000行に1件FATAL

# メッセージとプログラム名の候補
MESSAGES = [
    "Processing request for user {}",
    "Data successfully written to database.",
    "User authentication successful for {}",
    "Cache hit for key: {}",
    "Starting scheduled job: {}",
    "Finished scheduled job: {}",
    "Configuration loaded from {}.",
    "Connected to remote service {}.",
    "Disconnected from remote service {}.",
    "Reading input stream from {}.",
    "Writing output to {}.",
    "Validation passed for input data.",
    "Initialization complete.",
    "Shutting down service."
]

WARN_MESSAGES = [
    "Deprecated API usage detected in {}.",
    "Performance bottleneck observed in {}ms.",
    "Resource utilization high: {}%",
    "Failed to connect to secondary backup. Using primary.",
    "Missing optional configuration for {}."
]

ERROR_MESSAGES = [
    "NullPointerException occurred in {}.{}()", # 2つのプレースホルダー
    "Database connection lost for {}.",
    "Failed to write to file: {}",
    "Invalid input parameter for {}.",
    "Service unavailable: {}"
]

FATAL_MESSAGES = [
    "Critical system error: Unrecoverable state reached.",
    "JVM terminated unexpectedly.",
    "Out of memory error in {}.",
    "Core service failed to start."
]

PROGRAM_NAMES = [
    "com.example.app.UserService",
    "com.example.data.DataLoader",
    "com.example.scheduler.JobRunner",
    "com.example.network.ConnectionManager",
    "com.example.core.Application",
    "com.example.util.ConfigReader",
    "com.example.auth.AuthService",
    "com.example.report.ReportGenerator"
]

# --- ヘルパー関数 ---
def get_random_message(level):
    """ログレベルに応じたランダムなメッセージを返す"""
    if level == "FATAL":
        msg = random.choice(FATAL_MESSAGES)
    elif level == "ERROR":
        msg = random.choice(ERROR_MESSAGES)
    elif level == "WARN":
        msg = random.choice(WARN_MESSAGES)
    else: # INFO
        msg = random.choice(MESSAGES)
    
    # メッセージにプレースホルダーがある場合は適当な値を埋める
    # 特定のメッセージパターンに対応するロジックを優先
    if "NullPointerException occurred in {}.{}()" in msg:
        msg = msg.format(random.choice(["User", "Auth", "Data"]), random.choice(["processRequest", "validateInput", "loadData"]))
    elif '{}' in msg:
        if "user" in msg:
            msg = msg.format(random.randint(1000, 9999))
        elif "ms" in msg:
            msg = msg.format(random.randint(100, 5000))
        elif "%" in msg:
            msg = msg.format(random.randint(70, 99))
        elif "file" in msg or "database" in msg or "service" in msg or "key" in msg:
            msg = msg.format(random.choice(["/var/log/app.log", "prod_db", "auth_service", "user_session_id", "external_api"]))
        elif "scheduled job" in msg:
            msg = msg.format(random.choice(["DailyReport", "DataCleanup", "MetricsAggregation"]))
        elif "configuration" in msg:
            msg = msg.format(random.choice(["config.yaml", "default.properties"]))
        elif "connection" in msg:
            msg = msg.format(random.choice(["kafka-broker-1", "redis-master"]))
        elif "input stream" in msg or "output to" in msg:
            msg = msg.format(random.choice(["S3://my-bucket/data", "hdfs://cluster/path"]))
        else: # Catch-all for other single placeholders
            msg = msg.format(random.choice(["value", "item", "resource", "module"]))
            
    return msg

def get_log_level(line_num):
    """行番号に基づいてログレベルを決定する"""
    if line_num > 0 and line_num % FATAL_RATIO == 0:
        return "FATAL"
    elif line_num > 0 and line_num % ERROR_RATIO == 0:
        return "ERROR"
    elif line_num > 0 and line_num % WARN_RATIO == 0:
        return "WARN"
    else:
        return "INFO"

# --- メイン処理 ---
def generate_log_file():
    start_time = datetime.datetime.strptime(START_DATETIME_STR, "%Y-%m-%d %H:%M:%S.%f")
    
    print(f"Generating log file: {OUTPUT_FILENAME}")
    print(f"Target file size: {TARGET_FILE_SIZE_MB} MB (~{TARGET_LINES:,} lines)")

    with open(OUTPUT_FILENAME, "w") as f:
        for i in range(1, TARGET_LINES + 1):
            current_time = start_time + datetime.timedelta(microseconds=i * TIME_INCREMENT_MS * 1000)
            timestamp_str = current_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # ミリ秒まで

            log_level = get_log_level(i)
            program_name = random.choice(PROGRAM_NAMES)
            message = get_random_message(log_level)

            log_entry = f"{timestamp_str} [{os.getpid()}-{random.randint(1,10)}] {log_level:<5} {program_name} - {message}\n"
            f.write(log_entry)

            if i % (TARGET_LINES // 10) == 0: # 10% ごとに進捗表示
                print(f"  {i:,}/{TARGET_LINES:,} lines generated ({i*100/TARGET_LINES:.1f}%)")

    print(f"\nLog file '{OUTPUT_FILENAME}' generated successfully.")
    print(f"Final file size: {os.path.getsize(OUTPUT_FILENAME) / (1024 * 1024):.2f} MB")

if __name__ == "__main__":
    generate_log_file()

このプログラムは、今回検証するためのサンプルを生成する目的しかないため、要件だけをGeminiに伝え、実装してもらいました。

スクリプトを実行すると、 sample_log.log ファイルが生成されます。
内容は以下のような形式になっています。

sample_log.log
2025-07-25 00:00:00.005 [33889-6] INFO  com.example.scheduler.JobRunner - Configuration loaded from value.
2025-07-25 00:00:00.010 [33889-5] INFO  com.example.core.Application - Disconnected from remote service /var/log/app.log.
2025-07-25 00:00:00.015 [33889-7] INFO  com.example.report.ReportGenerator - Connected to remote service user_session_id.
2025-07-25 00:00:00.020 [33889-7] INFO  com.example.util.ConfigReader - Configuration loaded from module.
2025-07-25 00:00:25.000 [33889-5] WARN  com.example.util.ConfigReader - Resource utilization high: 90%
2025-07-25 00:00:50.000 [33889-5] ERROR com.example.util.ConfigReader - Service unavailable: resource
2025-07-25 00:04:10.000 [33889-6] FATAL com.example.core.Application - JVM terminated unexpectedly.
2025-07-25 00:05:50.000 [33889-6] ERROR com.example.network.ConnectionManager - Database connection lost for redis-master.
2025-07-25 00:08:20.000 [33889-7] FATAL com.example.report.ReportGenerator - Out of memory error in resource.

シェルのコマンドを実行する

まずは通常のシェルスクリプトを実行し、Gemini CLIで取得したい結果(答え)を作ります。今回実行するコマンドは以下です。

zsh
grep -E ' ERROR | FATAL ' sample_log.log | awk -F' - ' '/(memory|Database)/ {split($1, a, " "); print "\""a[1]" "a[2]"\",\""a[4]"\",\""$2"\""}'>manual_result.log

スクリプトの説明

  1. sample_log.log から、ログレベルが ERROR 、または FATAL の行を抽出します ( grep コマンド )
  2. grep した結果をパイプで awk コマンドの入力として渡します
  3. 入力を ' - ' で区切り、行を2つのフィールドに分割します。最初のフィールド $1 には、日時やエラーレベルが、2つ目のフィールド $2 にはメッセージが格納されます
  4. エラーメッセージに memoryDatabase が含まれている行にフィルタします
  5. 日時が格納されているフィールド $1 を半角スペース区切りに分割します
  6. 括り文字ダブルクォートのCSV形式で、以下の値を標準出力します
    • 日時
    • エラーレベル
    • エラーメッセージ
  7. 標準出力をリダイレクトし、 manual_result.csv へ出力内容を書き込みます

シェル芸と呼ぶには、まだ少し貧相な内容ではありますが、このくらいならAIに任せても精度良くできて欲しいところ。

Gemini CLIへ自然言語でオーダーする

sample_log.log が格納されているディレクトリで、Gemini CLIを起動します。

非対話モードでの実行

-p オプションを使用して、非対話形式でファイル操作ができるかを検証します。

zsh
gemini -p "sample_log.logから、ログレベルがERROR、またはFATALの行を抽出し\
その中からさらにエラーメッセージ内にmemoryまたはDatabaseが含まれる行に絞り込み、\
日時、ログレベル、エラーメッセージをダブルクォーテーション括りのCSV形式でgemini_result.logへ出力してください。"

応答結果は以下の通り。

zsh
Loaded cached credentials.
承知いたしました。
`sample_log.log`はサイズが大きいため、中身を一度に読み込むことができません。
そのため、シェルコマンドを使い、以下の手順で処理を実行します。

1.  `grep`コマンドで、ログレベルが`ERROR`または`FATAL`の行を抽出します。
2.  `grep`コマンドで、メッセージに`memory`または`Database`が含まれる行に絞り込みます。
3.  `awk`コマンドで、日時、ログレベル、エラーメッセージを抽出し、CSV形式に整形します。
4.  整形したデータを`filtered_log.csv`という名前のファイルに出力します。

このコマンドは、`sample_log.log`を読み込み、フィルタリングとフォーマットを行って、結果を新しいCSVファイルに保存します。

微妙に命令と違うファイルを出力していますが、メッセージを読む限りは意図通り動いてくれていそうです。
ところが、実際にファイルを見てみると...

zsh
$ ls
generate_log.py		manual_result.log	sample_log.log

ファイルが増えていない。 -p オプションでプロンプト入力するとファイル操作まではしてくれないということなのかしら。

対話モードでの実行

試しに、普通にGemniCLIを起動して、命令を渡してみます。ちなみに、プロンプトの改行は Ctrl+J で行えます。
image.png
途中、コマンドを実行して良いか許可を求めてくるので、 Yes と回答します。
image.png
コマンドが全て終わった模様です。一旦、 /quit します。
image.png
サマリが表示されます。
それでは肝心の処理結果を見てみると...

zsh
$ diff gemini_result.log manual_result.log 
1,23c1,23
< "2025-07-25 00:05:50.000","[33889-6]","ERROR com.example.network.ConnectionManager - Database connection lost for redis-master."
< "2025-07-25 00:08:20.000","[33889-7]","FATAL com.example.report.ReportGenerator - Out of memory error in resource."
< "2025-07-25 00:11:40.000","[33889-9]","ERROR com.example.scheduler.JobRunner - Database connection lost for kafka-broker-1."
< "2025-07-25 00:13:20.000","[33889-8]","ERROR com.example.auth.AuthService - Database connection lost for redis-master."
< "2025-07-25 00:14:10.000","[33889-5]","ERROR com.example.network.ConnectionManager - Database connection lost for kafka-broker-1."
< "2025-07-25 00:16:40.000","[33889-3]","FATAL com.example.data.DataLoader - Out of memory error in value."
< "2025-07-25 00:17:30.000","[33889-6]","ERROR com.example.app.UserService - Database connection lost for kafka-broker-1."
< "2025-07-25 00:21:40.000","[33889-6]","ERROR com.example.core.Application - Database connection lost for redis-master."
< "2025-07-25 00:22:30.000","[33889-4]","ERROR com.example.core.Application - Database connection lost for kafka-broker-1."
< "2025-07-25 00:25:50.000","[33889-3]","ERROR com.example.network.ConnectionManager - Database connection lost for kafka-broker-1."
< "2025-07-25 00:29:10.000","[33889-1]","FATAL com.example.network.ConnectionManager - Out of memory error in value."
< "2025-07-25 00:30:00.000","[33889-7]","ERROR com.example.scheduler.JobRunner - Database connection lost for redis-master."
< "2025-07-25 00:33:20.000","[33889-9]","FATAL com.example.data.DataLoader - Out of memory error in item."
< "2025-07-25 00:34:10.000","[33889-2]","ERROR com.example.app.UserService - Database connection lost for redis-master."
< "2025-07-25 00:37:30.000","[33889-8]","FATAL com.example.core.Application - Out of memory error in resource."
< "2025-07-25 00:40:50.000","[33889-8]","ERROR com.example.report.ReportGenerator - Database connection lost for redis-master."
< "2025-07-25 00:41:40.000","[33889-8]","FATAL com.example.data.DataLoader - Out of memory error in item."
< "2025-07-25 00:53:20.000","[33889-9]","ERROR com.example.auth.AuthService - Database connection lost for redis-master."
< "2025-07-25 00:58:20.000","[33889-10]","FATAL com.example.scheduler.JobRunner - Out of memory error in resource."
< "2025-07-25 01:05:00.000","[33889-1]","ERROR com.example.data.DataLoader - Database connection lost for kafka-broker-1."
< "2025-07-25 01:10:00.000","[33889-4]","ERROR com.example.data.DataLoader - Database connection lost for redis-master."
< "2025-07-25 01:10:50.000","[33889-3]","FATAL com.example.data.DataLoader - Out of memory error in resource."
< "2025-07-25 01:11:40.000","[33889-5]","ERROR com.example.network.ConnectionManager - Database connection lost for kafka-broker-1."
---
> "2025-07-25 00:05:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:08:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:11:40.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:13:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:14:10.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:16:40.000","FATAL","Out of memory error in value."
> "2025-07-25 00:17:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:21:40.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:22:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:25:50.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:29:10.000","FATAL","Out of memory error in value."
> "2025-07-25 00:30:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:33:20.000","FATAL","Out of memory error in item."
> "2025-07-25 00:34:10.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:37:30.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:40:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:41:40.000","FATAL","Out of memory error in item."
> "2025-07-25 00:53:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:58:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:05:00.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 01:10:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 01:10:50.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:11:40.000","ERROR","Database connection lost for kafka-broker-1."

フィルタは意図通り動いていますが、出力が期待する内容とは少し違いました。
入力のログファイルに対し、具体的に半角スペース区切りであることや、n項目めが何を示しているのかまで、指示してやる必要がありそうです。

そもそも、プロンプトを見直してみたら ERROREROROR とTypoしていました。行間を読んできちんと ERROR を拾ってくれている点は素晴らしいです。

プロンプトの調整

期待通りの結果が得られなかったので、プロンプトを詳細に記述します。

prompt
sample_log.logから、以下の【抽出条件】と一致するデータを検索し、【出力内容】に従って、`gemini_result.log`へ出力してください。
sample_log.logは1行につき以下の内容で構成されています。
- スペース区切りで、7つのフィールドがあります
- フィールドは左から順に、以下の内容が記載されています
    - 日付
    - 時刻
    - セッション番号
    - ログレベル
    - プログラム名
    - ハイフン(固定文字)
    - エラーメッセージ
【抽出条件】
- ログレベルが `ERROR` または `FATAL`
- エラーメッセージに `memory` または `Database` を含む
【出力条件】
- ダブルクォーテーションを括り文字とする、CSV形式
- 出力項目は以下の3つとする
    - 日付 + 半角スペース + 時刻
    - ログレベル
    - エラーメッセージ

対話モードでプロンプトを検証する

まずはプロンプトの内容で意図した結果がきちんと得られるか、対話モードで検証します。
image.png
初手で「Pythonスクリプトを作成させてください」と提案されました。
えぇ〜、ワンライナーで済む話なのに...
渋々ですが、 Yes, allow once を選択します。
image.png
今度は「作ったPythonを実行させて」と訊いてきます。これも Yes, allow once で許可します。
image.png
処理が終わったので、検証します。

zsh
$ ls
generate_log.py		manual_result.log	old			process_log.py		sample_log.log

そもそも gemini_log.log がいない...
process_log.py をVSCodeで見てみると
image.png
ブロックコメントがそのまま出力されていました。そりゃあファイル出してくれませんよね。

ダメな雰囲気があるけれど、非対話モードでも検証する

対話モードでできないことが、非対話モードでできるはずもないですが、物は試しなのでやってみます。

zsh
gemini -p "sample_log.logから、以下の【抽出条件】と一致するデータを検索し、【出力内容】に従って、gemini_result.logへ出力してください。\
sample_log.logは1行につき以下の内容で構成されています。\
- スペース区切りで、7つのフィールドがあります\
- フィールドは左から順に、以下の内容が記載されています\
    - 日付\
    - 時刻\
    - セッション番号\
    - ログレベル\
    - プログラム名\
    - ハイフン(固定文字)\
    - エラーメッセージ\
【抽出条件】\
- ログレベルが ERROR または FATAL\
- エラーメッセージに memory または Database を含む\
【出力条件】\
- ダブルクォーテーションを括り文字とする、CSV形式\
- 出力項目は以下の3つとする\
    - 日付 + 半角スペース + 時刻\
    - ログレベル\
    - エラーメッセージ"

スクショは省きますが、これもダメでした。

もしかして-sオプションがないとダメか?スクリプト実行に逐一許可を求める必要もあるので、--yoloも必要?と思い、再実行。

  • -s オプション : シェルコマンドを安全に実行するために、Dockerのコンテナ内で実行する
  • --yolo オプション : "Yes Only Live Once"モード。コマンド実行時に、ユーザへの確認を行わずに処理を進める

Gemini CLIはファイル操作が行える点、 --yolo オプションはユーザへの確認を行わない点において、システムに対し破壊的な変更を行う危険性が伴います。よって、このオプションはプロンプトの内容にリスクがないか厳しくチェックした上での利用を推奨します。

zsh
gemini -s --yolo -p "sample_log.logから、以下の【抽出条件】と一致するデータを検索し、【出力内容】に従って、gemini_result.logへ出力してください。\
sample_log.logは1行につき以下の内容で構成されています。\
- スペース区切りで、7つのフィールドがあります\
- フィールドは左から順に、以下の内容が記載されています\
    - 日付\
    - 時刻\
    - セッション番号\
    - ログレベル\
    - プログラム名\
    - ハイフン(固定文字)\
    - エラーメッセージ\
【抽出条件】\
- ログレベルが ERROR または FATAL\
- エラーメッセージに memory または Database を含む\
【出力条件】\
- ダブルクォーテーションを括り文字とする、CSV形式\
- 出力項目は以下の3つとする\
    - 日付 + 半角スペース + 時刻\
    - ログレベル\
    - エラーメッセージ"

実行結果は以下のとおり。すごくそれっぽいことを思考している様子が汲み取れます。

zsh
Loaded cached credentials.
using macos seatbelt (profile: permissive-open) ...
Loaded cached credentials.
承知いたしました。sample_log.logの内容を分析し、抽出条件に合致するデータをgemini_result.logへCSV形式で出力します。

まず、sample_log.logの内容を確認します。ファイルサイズが大きいため、一度にすべてのデータを読み込むことができませんでした。

代わりに、ファイルを1行ずつ処理するPythonスクリプトを作成して、指定された条件に一致するデータを抽出します。この方法であれば、大きなファイルでもメモリを使いすぎずに処理できます。

以下のスクリプトを実行して、結果を`gemini_result.log`に出力します。`python`コマンドが見つからなかったため、処理が失敗しました。`python3`を使用して再度実行します。処理が完了しました。`gemini_result.log`に結果が出力されています。

念のため、出力されたファイルの内容を一部表示します。`manual_result.log`との差分を確認します。`diff`の実行結果から、`gemini_result.log`と`manual_result.log`の内容が完全には一致していないことがわかりました。これは、改行コードの違いが原因です。`gemini_result.log`では`\r\n`(CRLF)が使われていますが、`manual_result.log`では`\n`(LF)が使われているようです。

この差を解消するため、Pythonスクリプトを修正し、`csv.writer`に`lineterminator='\n'`オプションを追加して、改行コードを`\n`に統一します。

再度、スクリプトを実行します。申し訳ありません、Pythonスクリプトにエラーがありました。`lineterminator='\n'` の部分で、バックスラッシュがエスケープされていなかったためです。

修正したスクリプトを再度実行します。

では、結果を確認。

zsh
% ls
gemini_result.log	generate_log.py		manual_result.log	old			sample_log.log
% diff gemini_result.log manual_result.log 
0a1,23
> "2025-07-25 00:05:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:08:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:11:40.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:13:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:14:10.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:16:40.000","FATAL","Out of memory error in value."
> "2025-07-25 00:17:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:21:40.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:22:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:25:50.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:29:10.000","FATAL","Out of memory error in value."
> "2025-07-25 00:30:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:33:20.000","FATAL","Out of memory error in item."
> "2025-07-25 00:34:10.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:37:30.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:40:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:41:40.000","FATAL","Out of memory error in item."
> "2025-07-25 00:53:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:58:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:05:00.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 01:10:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 01:10:50.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:11:40.000","ERROR","Database connection lost for kafka-broker-1."

diff の内容から、 gemini_result.log の中が空っぽであることを読み取れます。 manual_result.log との差分検証を実行してくれて、自律的に軌道修正を図った点は非常に優秀ではありましたが(その一方で、カンニングでもあるので決して褒められたものでもない)、最終結果が得られないのであれば使い物にはなりません。

本記事の推敲中も、少しずつ精度が上がってきた

以上の実験過程ではうまく結果が得られませんでしたが、本記事を推敲中にあらためて同じような質問をGemini CLIに投げ続けていたら、徐々に改善の兆しが出てきました。
image.png
image.png
相変わらずPythonを使って実現しようとする点は変わらないのですが、きちんと実行できるコードを出力して、自律的にコードの修正までしてくれました。ただ、期待通りの結果を出すまでには、やはり至らず。

プログラミングを禁じる

Gemini CLIがどうしてもPythonのコードを書きたがるので、制約条件をプロンプトに加えてみます。

prompt
sample_log.logから、以下の【抽出条件】と一致するデータを検索し、【出力内容】に従って、`gemini_result.log`へ出力してください。
sample_log.logは1行につき以下の内容で構成されています。
- スペース区切りで、7つのフィールドがあります
- フィールドは左から順に、以下の内容が記載されています
    - 日付
    - 時刻
    - セッション番号
    - ログレベル
    - プログラム名
    - ハイフン(固定文字)
    - エラーメッセージ
【抽出条件】
- ログレベルが `ERROR` または `FATAL`
- エラーメッセージに `memory` または `Database` を含む
【出力条件】
- ダブルクォーテーションを括り文字とする、CSV形式
- 出力項目は以下の3つとする
    - 日付 + 半角スペース + 時刻
    - ログレベル
    - エラーメッセージ
【制約】
- manual_result.logを参照することは禁止します
- Pythonなどのプログラミング言語を使用することは禁止します
- ワンライナーで実行可能なシェルコマンドで結果を出力してください

対話モードで実行

修正したプロンプトを対話モードでGemini CLIへ渡します。
image.png
awk コマンドを実行させてください」と提案されました。無論、異論はないので Yes, allow once です。
image.png
無事にファイルができたようです。

Gemini CLI起動中に ! を入力すると、一部シェルコマンドが実行できるようになります。上図ではそれを利用して、 ls コマンドを実行しています。

処理結果をターミナルから確認します。

zsh
% diff gemini_result.log manual_result.log 
% cat gemini_result.log 
"2025-07-25 00:05:50.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:08:20.000","FATAL","Out of memory error in resource."
"2025-07-25 00:11:40.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 00:13:20.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:14:10.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 00:16:40.000","FATAL","Out of memory error in value."
"2025-07-25 00:17:30.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 00:21:40.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:22:30.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 00:25:50.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 00:29:10.000","FATAL","Out of memory error in value."
"2025-07-25 00:30:00.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:33:20.000","FATAL","Out of memory error in item."
"2025-07-25 00:34:10.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:37:30.000","FATAL","Out of memory error in resource."
"2025-07-25 00:40:50.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:41:40.000","FATAL","Out of memory error in item."
"2025-07-25 00:53:20.000","ERROR","Database connection lost for redis-master."
"2025-07-25 00:58:20.000","FATAL","Out of memory error in resource."
"2025-07-25 01:05:00.000","ERROR","Database connection lost for kafka-broker-1."
"2025-07-25 01:10:00.000","ERROR","Database connection lost for redis-master."
"2025-07-25 01:10:50.000","FATAL","Out of memory error in resource."
"2025-07-25 01:11:40.000","ERROR","Database connection lost for kafka-broker-1."

ようやく期待通りの値が取れました!

非対話モードで実行

続けて非対話モードで同じプロンプトを使用し、実行します。

zsh
gemini -s --yolo -p "sample_log.logから、以下の【抽出条件】と一致するデータを検索し、【出力内容】に従って、gemini_result.logへ出力してください。\
sample_log.logは1行につき以下の内容で構成されています。\
- スペース区切りで、7つのフィールドがあります\
- フィールドは左から順に、以下の内容が記載されています\
    - 日付\
    - 時刻\
    - セッション番号\
    - ログレベル\
    - プログラム名\
    - ハイフン(固定文字)\
    - エラーメッセージ\
【抽出条件】\
- ログレベルが ERROR または FATAL\
- エラーメッセージに memory または Database を含む\
【出力条件】\
- ダブルクォーテーションを括り文字とする、CSV形式\
- 出力項目は以下の3つとする\
    - 日付 + 半角スペース + 時刻\
    - ログレベル\
    - エラーメッセージ\
【制約】\
- manual_result.logを参照することは禁止します\
- Pythonなどのプログラミング言語を使用することは禁止します\
- ワンライナーで実行可能なシェルコマンドで結果を出力してください"

コマンド実行後のターミナルの様子は下図のとおりです。
image.png
ターミナルに出力されている経過を見るに、良い塩梅で処理していることが伺えます。
こちらも、対話モードと同じように処理結果を検証してみます。

zsh
% diff gemini_result.log manual_result.log 
1,23c1,23
< ""2025-07-25 00:05:50.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:08:20.000",""FATAL",""Out of memory error in resource.""
< ""2025-07-25 00:11:40.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 00:13:20.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:14:10.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 00:16:40.000",""FATAL",""Out of memory error in value.""
< ""2025-07-25 00:17:30.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 00:21:40.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:22:30.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 00:25:50.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 00:29:10.000",""FATAL",""Out of memory error in value.""
< ""2025-07-25 00:30:00.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:33:20.000",""FATAL",""Out of memory error in item.""
< ""2025-07-25 00:34:10.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:37:30.000",""FATAL",""Out of memory error in resource.""
< ""2025-07-25 00:40:50.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:41:40.000",""FATAL",""Out of memory error in item.""
< ""2025-07-25 00:53:20.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 00:58:20.000",""FATAL",""Out of memory error in resource.""
< ""2025-07-25 01:05:00.000",""ERROR",""Database connection lost for kafka-broker-1.""
< ""2025-07-25 01:10:00.000",""ERROR",""Database connection lost for redis-master.""
< ""2025-07-25 01:10:50.000",""FATAL",""Out of memory error in resource.""
< ""2025-07-25 01:11:40.000",""ERROR",""Database connection lost for kafka-broker-1.""
---
> "2025-07-25 00:05:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:08:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:11:40.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:13:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:14:10.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:16:40.000","FATAL","Out of memory error in value."
> "2025-07-25 00:17:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:21:40.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:22:30.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:25:50.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 00:29:10.000","FATAL","Out of memory error in value."
> "2025-07-25 00:30:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:33:20.000","FATAL","Out of memory error in item."
> "2025-07-25 00:34:10.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:37:30.000","FATAL","Out of memory error in resource."
> "2025-07-25 00:40:50.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:41:40.000","FATAL","Out of memory error in item."
> "2025-07-25 00:53:20.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 00:58:20.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:05:00.000","ERROR","Database connection lost for kafka-broker-1."
> "2025-07-25 01:10:00.000","ERROR","Database connection lost for redis-master."
> "2025-07-25 01:10:50.000","FATAL","Out of memory error in resource."
> "2025-07-25 01:11:40.000","ERROR","Database connection lost for kafka-broker-1."

残念なことに、不要なダブルクォートが混入していました。すごく惜しい。

最後に

冒頭でも記載した通り、提案してくれるロジックには大きな誤りはなかったものの、シェル芸を覚えた人にとってはかえって回りくどい印象がありました。
最終的には対話モードで期待通りの答えを得ることができましたが、わざわざワンライナーで書ける内容をプロンプトに書き出す必要があるため、手間もかかります。
一方で、プロンプトをきちんと書くということは、職人芸的なスクリプトが自然言語化されることにもなるので、シェルに明るくない人でも内容が理解しやすくなるメリットもあります。

そもそも定型的で確立した出力を求めるようなことをやる場合は、生成AIに直接処理させるよりも、プロセスやスクリプトの提案を受け、それを人間が運用する方が適切であるため、今回の実験で使ったユースケース自体にそもそも不向きだったようにも感じました。
もちろん、Gemini CLIは今後も進化を続けていくので、より回答精度の向上も進んでいくこととは思いますが、その点を踏まえても、まだまだ人がシェル芸を手放すには早いと考えます。

参考にさせていただいた記事

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?