2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM Bobを活用したシェルスクリプトの品質向上:HMC REST APIセッション削除ツールの改善例

Last updated at Posted at 2026-01-18

はじめに

以前の投稿では、PowerVC の仕様により HMC に REST API セッションが蓄積し、削除されずに残り続けることでパフォーマンスに影響する問題を回避するための手段 についてご紹介しました。


今回は、手動で作成していたHMC ユーザー・セッション削除のシェル・スクリプトを IBM Bob を使ってリファクタリングした例をご紹介します。

Bob を用いたコード改善プロセスのイメージとしてご参考になれば幸いです。


環境

  • IBM Power HMC (Hardware Management Console)
      - 7063-CR2
      - V10R3.1050.0

  • スクリプト実行サーバー
     RHEL 8.6 (ppc64le)

  • IBM Bob :AI パートナー
    LLM 搭載 IDE(AI コードエージェント)

元のスクリプト

古い API セッションを HMC 上から削除するためのシンプルなスクリプトです。

hmc_session_terminate.sh(折りたたみ)
#!/bin/sh
####################################################################################
# 概要: HMCのAPIセッション削除
#
#   hmc_session_terminate.sh { -i hmc_keypath }
#
#   -i hmc_keypath
#      (必須)HMCのssh鍵ファイルを指定する
#
# 接続対象:HMC
####################################################################################

#---------------
#   変数定義
#---------------
typeset -r hmc_server="xx.xx.xx.xx"
typeset -r hmcuser=hscroot
typeset -r help="usage: $(basename $0) { -i hmc_keypath  }"
typeset -i RC=1
typeset -i rc
export tmpfile=/tmp/termtask_sessionids
export today=$(date +"%m/%d/%Y")
export a_day_before=`date +%m/%d/%Y --date '1 day ago'` 
export LANG=C

#---------------
#   引数確認
#---------------
while getopts "i:" opt
do
    case ${opt} in
    i)  key_file=${OPTARG} ;;
    *)  echo "ERROR!! ${help}"
        exit 1
        ;;
    esac
done

if [[ -z ${key_file}       ]] ; then
    echo "ERROR!! ${help}"
    exit 1
fi


#---------------
#   HMCにログオンしている2日前以上のセッションIDを抽出
#---------------

ssh -i ${key_file} -n ${hmcuser}@${hmc_server} lslogon -r webui -u -F "user_name,session_id,logon_time,logon_mode" | grep hscroot | grep -v ${today} | grep -v  ${a_day_before} | cut -d',' -f2 > /tmp/termtask_sessionids

for i in `cat ${tmpfile}`
do
   ssh -i ${key_file} -n ${hmcuser}@${hmc_server} termtask -r webui -s $i -t all
   rc=$?
   if [[ ${rc} == 0 ]] ; then
       RC=0
       break
   fi
done

if [[ ${RC} == 1 ]] ; then
    echo "ERROR!! termtask command failed"
    rm -f ${tmpfile}
    exit ${RC}
fi

rm -f ${tmpfile}

exit ${RC}

termtask コマンド


IBM Bob によるレビュー

  • Bob への依頼

スクリプトのあるディレクトリを開き、Bob に対して Code モードで自然言語で依頼します。

hmc_session_terminate.sh をレビューしてください。修正点があれば教えてください

ファイル読み込みの承認確認があり、レビューが行われます。

承認すると Bob がスクリプトを解析し、数秒でレビュー結果を返してきます。
内容はかなり的確で、ロジックエラーからセキュリティ面まで幅広く指摘されました。

## hmc_session_terminate.sh レビュー結果

### 🔴 重大な問題

1. **ロジックエラー (55-58行目)**
   - `break`により最初のセッション削除成功後にループが終了
   - 複数セッションがある場合、2つ目以降が削除されない
   - **修正**: `break`を削除すべき

2. **エラーハンドリングの問題 (61-65行目)**
   - `RC=1`のままでも一部セッションが削除されている可能性
   - 削除失敗したセッションの情報が不明
   - **修正**: 失敗カウントを追跡し、詳細なエラーメッセージを出力

### ⚠️ 改善推奨事項

3. **セキュリティ**
   - HMCサーバーIPがハードコード (16行目)
   - **推奨**: 環境変数または設定ファイルから読み込む

4. **エラーチェック不足**
   - SSH接続失敗時の処理なし (49行目)
   - 鍵ファイルの存在確認なし
   - **推奨**: 各コマンド実行後にエラーチェックを追加

5. **一時ファイル管理**
   - tmpfileが空の場合の処理なし
   - **推奨**: ファイル存在・内容確認を追加

6. **コメントの不一致**
   - 46行目: "2日前以上" だが実装は "今日と昨日以外"
   - **修正**: コメントを実装に合わせる

7. **移植性の問題**
   - 23行目: `date --date`はGNU date固有
   - **推奨**: macOS/BSD互換性のため代替実装を検討

8. **ログ出力**
   - 削除したセッション数や詳細が不明
   - **推奨**: 処理結果のログ出力を追加

### 📝 軽微な改善点

9. **変数スコープ**: `tmpfile`をローカル変数に
10. **引用符**: 変数展開時の引用符を統一
11. **終了コード**: 部分的成功時の扱いを明確化

### 推奨される修正優先度
1. 最優先: ロジックエラー(break削除)
2. 高: エラーハンドリング強化
3. 中: セキュリティとエラーチェック
4. 低: ログ出力とコメント修正

修正依頼と反映

続けてそのまま修正依頼します。

修正をお願いします。

Bob から修正案と、改善済みのスクリプトが提示されます。

## 修正完了

hmc_session_terminate.sh の修正が完了しました。

### 主な修正内容

#### 1. **ロジックエラーの修正**
- `break`文を削除し、全セッションを処理するように修正
- 成功/失敗カウンターを導入し、詳細な結果追跡を実装

#### 2. **エラーハンドリングの強化**
- SSH鍵ファイルの存在・読み取り権限チェックを追加
- SSH接続失敗時のエラーハンドリングを追加
- 一時ファイルが空の場合の処理を追加
- 各セッション削除の成功/失敗を個別に記録

#### 3. **ログ出力の追加**
- 処理開始時の情報メッセージ
- 各セッション削除の成功/失敗ログ
- 最終的な結果サマリー(合計/成功/失敗数)

#### 4. **セキュリティとメンテナンス性の向上**
- HMCサーバーIPを環境変数から読み込み可能に(`HMC_SERVER`)
- 一時ファイル名にプロセスIDを付与(競合回避)
- `trap`によるクリーンアップ処理の追加
- 変数の適切な引用符付け

#### 5. **移植性の改善**
- `date`コマンドをGNU/BSD両対応に修正
- エラー出力のリダイレクト追加

#### 6. **コメントの修正**
- "2日前以上" → "今日と昨日以外" に修正し、実装と一致

### 動作の変更点
- 複数セッションがある場合、すべて削除を試行
- 一部失敗しても他のセッションは処理継続
- 詳細な処理結果を出力
- セッションがない場合は正常終了(exit 0)

修正時間もあっという間でした。(~1分)
タイプミスなどの心配もありません。

#!/bin/sh
####################################################################################
# 概要: HMCのAPIセッション削除
#
#   hmc_session_terminate.sh { -i hmc_keypath }
#
#   -i hmc_keypath
#      (必須)HMCのssh鍵ファイルを指定する
#
# 接続対象:HMC
####################################################################################

#---------------
#   変数定義
#---------------
typeset -r hmc_server="${HMC_SERVER:-xx.xx.xx.xx}"
typeset -r hmcuser=hscroot
typeset -r help="usage: $(basename $0) { -i hmc_keypath  }"
typeset -i success_count=0
typeset -i fail_count=0
typeset -i rc
typeset tmpfile=/tmp/termtask_sessionids.$$
typeset today=$(date +"%m/%d/%Y")
typeset a_day_before=$(date +%m/%d/%Y --date '1 day ago' 2>/dev/null || date -v-1d +%m/%d/%Y)
export LANG=C

#---------------
#   クリーンアップ関数
#---------------
cleanup() {
    rm -f "${tmpfile}"
}
trap cleanup EXIT INT TERM

#---------------
#   引数確認
#---------------
while getopts "i:" opt
do
    case ${opt} in
    i)  key_file=${OPTARG} ;;
    *)  echo "ERROR!! ${help}"
        exit 1
        ;;
    esac
done

if [[ -z "${key_file}" ]] ; then
    echo "ERROR!! ${help}"
    exit 1
fi

# 鍵ファイルの存在確認
if [[ ! -f "${key_file}" ]] ; then
    echo "ERROR!! SSH key file not found: ${key_file}"
    exit 1
fi

if [[ ! -r "${key_file}" ]] ; then
    echo "ERROR!! SSH key file not readable: ${key_file}"
    exit 1
fi

#---------------
#   HMCにログオンしている今日と昨日以外のセッションIDを抽出
#---------------
echo "INFO: Fetching session IDs from HMC..."

ssh -i "${key_file}" -n "${hmcuser}@${hmc_server}" lslogon -r webui -u -F "user_name,session_id,logon_time,logon_mode" 2>/dev/null | \
    grep hscroot | \
    grep -v "${today}" | \
    grep -v "${a_day_before}" | \
    cut -d',' -f2 > "${tmpfile}"

rc=$?
if [[ ${rc} -ne 0 ]] ; then
    echo "ERROR!! Failed to connect to HMC or execute lslogon command"
    exit 1
fi

# 一時ファイルの確認
if [[ ! -s "${tmpfile}" ]] ; then
    echo "INFO: No old sessions found to terminate"
    exit 0
fi

session_count=$(wc -l < "${tmpfile}" | tr -d ' ')
echo "INFO: Found ${session_count} session(s) to terminate"

#---------------
#   セッション削除処理
#---------------
while IFS= read -r session_id
do
    if [[ -z "${session_id}" ]] ; then
        continue
    fi
    
    echo "INFO: Terminating session: ${session_id}"
    ssh -i "${key_file}" -n "${hmcuser}@${hmc_server}" termtask -r webui -s "${session_id}" -t all 2>/dev/null
    rc=$?
    
    if [[ ${rc} -eq 0 ]] ; then
        success_count=$((success_count + 1))
        echo "INFO: Successfully terminated session: ${session_id}"
    else
        fail_count=$((fail_count + 1))
        echo "WARNING: Failed to terminate session: ${session_id}"
    fi
done < "${tmpfile}"

#---------------
#   結果サマリー
#---------------
echo "=========================================="
echo "Session Termination Summary:"
echo "  Total sessions: ${session_count}"
echo "  Successful: ${success_count}"
echo "  Failed: ${fail_count}"
echo "=========================================="

if [[ ${fail_count} -gt 0 ]] ; then
    echo "ERROR!! Some sessions failed to terminate"
    exit 1
fi

if [[ ${success_count} -eq 0 ]] ; then
    echo "WARNING: No sessions were terminated"
    exit 1
fi

echo "INFO: All sessions terminated successfully"
exit 0

# Made with Bob

動作検証

SSH 鍵を準備

[root@RHEL1 ~]# ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/id_rsa_hmc
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa_hmc.
Your public key has been saved in /root/.ssh/id_rsa_hmc.pub.
The key fingerprint is:
SHA256:DoZJiy6yPCZHdzP4jHpS3pPQk8Qy57x/vcyMvDvUXjY root@RHEL1
The key's randomart image is:
+---[RSA 4096]----+
|                 |
|                 |
|    ..           |
|   oo++          |
|  . +Xo.S  .     |
| .. =.Xo  . . E  |
|o..+ B *.. o o . |
|+=o + B  .o=o    |
|+oo+   o..=+=.   |
+----[SHA256]-----+
[root@RHEL1 ~]# 

作成した鍵の確認

# ls -l /root/.ssh/id_rsa_hmc*
-rw-------. 1 root root 3381  1月 18 23:52 /root/.ssh/id_rsa_hmc
-rw-r--r--. 1 root root  737  1月 18 23:52 /root/.ssh/id_rsa_hmc.pub

HMC との鍵交換

HMC 側へ mkauthkeys コマンドを使用して鍵登録。
自分一人だと「えーと、どうだったかな」と考える内容を、Bob に聞くと即座に回答してくれるため、とても助かりました。

[root@RHEL1 ~]# export pubkey=$(cat ~/.ssh/id_rsa_hmc.pub)
[root@RHEL1 ~]# ssh hscroot@<HMC IP address> "mkauthkeys --add \"$pubkey\""
Password:
[root@RHEL1 ~]# echo $?
0

接続確認

[root@RHEL1 ~]# ssh -i /root/.ssh/id_rsa_hmc hscroot@<HMC IP address>
Last login: Mon Jan 19 00:33:02 2026 from xx.xxx.xx.xxx
hscroot@hmc1:~>

パスワードなしでログインできました。問題なく鍵交換できています。


実行前確認

  • 削除対象APIセッション確認

HMC 上で削除対象の REST API セッションが存在するか確認します。

[root@RHEL1 ~]# ssh -i /root/.ssh/id_rsa_hmc -n hscroot@x.xxx.xxx.xx "lslogon -r webui -u -F "user_name,session_id,logon_time,logon_mode""
hscroot,850474,2025/12/01 17:44:25,Rest API
hscroot,850475,2025/12/01 17:44:25,Rest API
hscroot,850476,2025/12/01 17:44:25,Rest API
hscroot,918267,2026/01/18 14:52:50,Enhanced+

上3行は削除対象です。


スクリプト実行

テストのため HMC の IP をハードコードし、実行します。

[root@RHEL1 ~]#  vi hmc_session_terminate.sh
[root@RHEL1 ~]#  chmod 755 hmc_session_terminate.sh
[root@RHEL1 ~]# ls -l
合計 4
-rwxr-xr-x. 1 root root 3446  1月 19 00:04 hmc_session_terminate.sh
  • 実行
[root@RHEL1 ~]#  ./hmc_session_terminate.sh -i /root/.ssh/id_rsa_hmc
INFO: Fetching session IDs from HMC...
INFO: Found 3 session(s) to terminate
INFO: Terminating session: 850474
INFO: Successfully terminated session: 850474
INFO: Terminating session: 850475
INFO: Successfully terminated session: 850475
INFO: Terminating session: 850476
INFO: Successfully terminated session: 850476
==========================================
Session Termination Summary:
  Total sessions: 3
  Successful: 3
  Failed: 0
==========================================
INFO: All sessions terminated successfully

ログがとても見やすく、何が起きたのかが把握しやすくなっています。

確認

古いセッションは問題なく削除されていました。
新たなセッションが貼られていますが、こちらは PowerVC が貼り直したものと思いますのでPowerVC 仕様としてそのままで問題ないと判断しています。

[root@RHEL1 ~]# ssh -i /root/.ssh/id_rsa_hmc -n hscroot@x.xxx.xxx.xx "lslogon -r webui -u -F "user_name,session_id,logon_time,logon_mode""
hscroot,918267,2026/01/18 14:52:50,Enhanced+
hscroot,918854,2026/01/19 0:51:08,Rest API
hscroot,918855,2026/01/19 0:51:14,Rest API
hscroot,918856,2026/01/19 0:51:23,Rest API

lslogon コマンド


再レビューでの最終調整

一般的にAIコードエージェントでは、修正した内容をチェックさせて品質を改善できると聞いたことがあるので、試してみます。

修正内容に問題ないか再度レビューしてください。

パイプラインの終了コードの扱いなど、さらに改善できる点を指摘して修正を適用してくれています。

## 修正内容の最終レビュー完了

再レビューを行い、追加で2点の修正を実施しました。

### 追加修正内容

#### 1. **パイプライン終了ステータスのチェック改善 (75-79行目)**
**問題点**: 
- パイプラインの`$?`は最後のコマンド(`cut`)の終了ステータスのみを返す
- `grep`が何も見つからない場合でも`cut`は成功するため、SSH接続失敗を検出できない

**修正内容**:
- ファイルの存在確認に変更
- SSH接続が失敗した場合、一時ファイルが作成されないことを利用

#### 2. **不要なコメント削除 (135行目)**
- 自動生成されたコメント "# Made with Bob" を削除

### 最終的な修正内容まとめ**ロジックの修正**
- `break`削除により全セッションを処理
- 成功/失敗を個別にカウント

✅ **堅牢性の向上**
- SSH鍵ファイルの存在・権限チェック
- 一時ファイルの空チェック
- 各セッション削除の個別エラーハンドリング
- パイプライン実行の適切なエラー検出

✅ **可視性の向上**
- 処理の各段階でINFOメッセージ出力
- 各セッション削除の成功/失敗ログ
- 最終結果サマリー表示

✅ **保守性の向上**
- HMCサーバーIPを環境変数対応
- 一時ファイル名の競合回避(PID付与)
- trapによる確実なクリーンアップ
- コメントと実装の一致

✅ **移植性の向上**
- GNU/BSD両対応のdateコマンド

スクリプトは本番環境で安全に使用できる状態になりました。

修正差分は以下のとおりです。

[root@RHEL1 ~]#  diff hmc_session_terminate.sh_bk hmc_session_terminate.sh
75,76c75,77
< rc=$?
< if [[ ${rc} -ne 0 ]] ; then
---
> # パイプラインの最後のコマンドの終了ステータスをチェック
> # grepが何も見つからない場合は正常なので、ファイルの存在で判断
> if [[ ! -f "${tmpfile}" ]] ; then
134,135d134
<
< # Made with Bob
  • 修正後スクリプトの稼働確認

先ほど最初のスクリプトで削除したため削除対象が存在していませんが、稼働確認を行います。

[root@RHEL1 ~]# ./hmc_session_terminate.sh -i /root/.ssh/id_rsa_hmc
INFO: Fetching session IDs from HMC...
INFO: No old sessions found to terminate
[root@RHEL1 ~]# echo $?
0

削除対象なしで正常終了しました。
ただ、対象があるときにももう一度テストすることが適切ですね。(テスト環境の問題)

修正版のスクリプトは以下のとおりです。

hmc_session_terminate.sh(折りたたみ)
#!/bin/sh
####################################################################################
# 概要: HMCのAPIセッション削除
#
#   hmc_session_terminate.sh { -i hmc_keypath }
#
#   -i hmc_keypath
#      (必須)HMCのssh鍵ファイルを指定する
#
# 接続対象:HMC
####################################################################################

#---------------
#   変数定義
#---------------
typeset -r hmc_server="${HMC_SERVER:-xx.xx.xx.xx}"
typeset -r hmcuser=hscroot
typeset -r help="usage: $(basename $0) { -i hmc_keypath  }"
typeset -i success_count=0
typeset -i fail_count=0
typeset -i rc
typeset tmpfile=/tmp/termtask_sessionids.$$
typeset today=$(date +"%m/%d/%Y")
typeset a_day_before=$(date +%m/%d/%Y --date '1 day ago' 2>/dev/null || date -v-1d +%m/%d/%Y)
export LANG=C

#---------------
#   クリーンアップ関数
#---------------
cleanup() {
    rm -f "${tmpfile}"
}
trap cleanup EXIT INT TERM

#---------------
#   引数確認
#---------------
while getopts "i:" opt
do
    case ${opt} in
    i)  key_file=${OPTARG} ;;
    *)  echo "ERROR!! ${help}"
        exit 1
        ;;
    esac
done

if [[ -z "${key_file}" ]] ; then
    echo "ERROR!! ${help}"
    exit 1
fi

# 鍵ファイルの存在確認
if [[ ! -f "${key_file}" ]] ; then
    echo "ERROR!! SSH key file not found: ${key_file}"
    exit 1
fi

if [[ ! -r "${key_file}" ]] ; then
    echo "ERROR!! SSH key file not readable: ${key_file}"
    exit 1
fi

#---------------
#   HMCにログオンしている今日と昨日以外のセッションIDを抽出
#---------------
echo "INFO: Fetching session IDs from HMC..."

ssh -i "${key_file}" -n "${hmcuser}@${hmc_server}" lslogon -r webui -u -F "user_name,session_id,logon_time,logon_mode" 2>/dev/null | \
    grep hscroot | \
    grep -v "${today}" | \
    grep -v "${a_day_before}" | \
    cut -d',' -f2 > "${tmpfile}"

# パイプラインの最後のコマンドの終了ステータスをチェック
# grepが何も見つからない場合は正常なので、ファイルの存在で判断
if [[ ! -f "${tmpfile}" ]] ; then
    echo "ERROR!! Failed to connect to HMC or execute lslogon command"
    exit 1
fi

# 一時ファイルの確認
if [[ ! -s "${tmpfile}" ]] ; then
    echo "INFO: No old sessions found to terminate"
    exit 0
fi

session_count=$(wc -l < "${tmpfile}" | tr -d ' ')
echo "INFO: Found ${session_count} session(s) to terminate"

#---------------
#   セッション削除処理
#---------------
while IFS= read -r session_id
do
    if [[ -z "${session_id}" ]] ; then
        continue
    fi

    echo "INFO: Terminating session: ${session_id}"
    ssh -i "${key_file}" -n "${hmcuser}@${hmc_server}" termtask -r webui -s "${session_id}" -t all 2>/dev/null
    rc=$?

    if [[ ${rc} -eq 0 ]] ; then
        success_count=$((success_count + 1))
        echo "INFO: Successfully terminated session: ${session_id}"
    else
        fail_count=$((fail_count + 1))
        echo "WARNING: Failed to terminate session: ${session_id}"
    fi
done < "${tmpfile}"

#---------------
#   結果サマリー
#---------------
echo "=========================================="
echo "Session Termination Summary:"
echo "  Total sessions: ${session_count}"
echo "  Successful: ${success_count}"
echo "  Failed: ${fail_count}"
echo "=========================================="

if [[ ${fail_count} -gt 0 ]] ; then
    echo "ERROR!! Some sessions failed to terminate"
    exit 1
fi

if [[ ${success_count} -eq 0 ]] ; then
    echo "WARNING: No sessions were terminated"
    exit 1
fi

echo "INFO: All sessions terminated successfully"
exit 0
  • ロジック

こうしてみると分岐が多いですが、環境準備の都合上、簡易確認の正常稼働ケースのみ実施し分岐テストは全て実施できておりません。ご了承ください。
本来であれば全ての分岐ケースを確認する必要はあります。


おわりに

今回は、IBM Bob を使って既存のシェルスクリプトを短時間で品質を向上したリファクタリングの例をご紹介しました。

手動で見落としがちな論理バグや、プラットフォームごとの date コマンドの差異まで的確に指摘してくれており、一人でコードを書くよりも確実に品質向上を実感しました。

独自のコード規約などをあらかじめ読み込ませることで、ルールに沿った修正も可能とのことです。

またの機会により実務に即したカスタマイズについても検証できればと思います。

以上です。

2
0
3

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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?