対話型CLIの自動化:Linuxで入力を求めるコマンドを処理する秘訣

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

対話型CLIという名の悩み

シェルスクリプトを書いていて、途中でパスワードの入力を求められるコマンドに遭遇するのは、まさに苦痛です。完全に自動化したいのに、ツールが「yes/no」の確認やパスワード入力を待って停止してしまうからです。代表的な例としては、passwdコマンド、鍵認証を設定していないssh、あるいは数十年前のソフトウェアのインストーラーなどが挙げられます。

私が管理しているUbuntu 22.04(RAM 4GB)のサーバーでは、かつて、ある古いFTPバックアップツールのために、深夜2時に起きてパスワードを手入力しなければなりませんでした。そのツールは引数でのパスワード渡しをサポートしていなかったのです。毎晩数文字を打つために待機するのは、時間の凄まじい浪費でした。そこで出会ったのがexpectです。

なぜ一般的な方法では失敗するのか?

expectを使う前に、いくつか近道を試してみましたが、うまくいきませんでした。

  • パイプの使用 (echo "pass" | command): この方法はsudopasswdでは失敗することが多いです。理由は、これらのセキュリティツールがstdin(標準入力)を無視し、/dev/tty(端末デバイス)から直接読み取ろうとするためです。
  • フラグの使用 (-y): apt install -yなどは非常に便利です。しかし現実には、多くの社内スクリプトやレガシーソフトウェアには、そのような「寛容な」フラグは存在しません。

Expect:ユーザーの役割を演じるツール

Expectは単なるテクニックではなく、一つの独立したプログラムです。これは対話シナリオに基づいて動作します。「あなたがAと言ったら、私はBと答える」という仕組みです。

半年間実務で運用してみて、いくつかの核心的なポイントが見えてきました:

  • メリット: あらゆる種類の対話を処理できます。分岐能力も非常に高く、「Error」が表示されたら終了し、「Success」なら続行するといった制御が可能です。特に、autoexpectツールを使えば、スクリプトを自動生成することもできます。
  • デメリット: Tcl(Tool Command Language)構文は、Bashに慣れた人には少し馴染みにくいかもしれません。また、ファイル内にパスワードをプレーンテキストで保存する場合のセキュリティリスクにも直面します。

インストールと基本コマンド

Ubuntu/Debianでは、わずか3秒でインストールが完了します。

sudo apt update && sudo apt install expect -y

expectのシナリオは、ほとんどの場合、以下の4つのコマンドを中心に構成されます:

  1. spawn: プロセスを起動する(例:SSH接続を開始する)。
  2. expect: 画面に特定のキーワードが表示されるのを待機する。
  3. send: データを送り込む(キーボードで入力するのと同じ)。
  4. interact: 必要に応じて、制御権をユーザー(あなた)に戻す。

例1:パスワード変更を一瞬で自動化

passwdコマンドは通常、パスワードを2回入力させる必要があります。以下のchange_password.expスクリプトは、それをスマートに処理します。

#!/usr/bin/expect -f

set user [lindex $argv 0]
set password [lindex $argv 1]

spawn passwd $user
expect "Enter new UNIX password:"
send "$password\r"
expect "Retype new UNIX password:"
send "$password\r"
expect "password updated successfully"
expect eof

ヒント: send文字列の最後にある\rを忘れないでください。これはEnterキーを押す動作に相当します。これがないと、スクリプトは無期限に停止してしまいます。

例2:鍵認証がない環境でのSSHログイン

SSH鍵認証が標準ですが、古いネットワーク機器などでパスワードログインを強制される場合があります。その処理方法は以下の通りです。

#!/usr/bin/expect -f

set timeout 10
spawn ssh [email protected]

expect {
    "yes/no" {
        send "yes\r"
        exp_continue
    }
    "password:" {
        send "Secret123\r"
    }
}

expect "$"
send "uptime\r"
send "exit\r"
expect eof

ここでは条件分岐構造を使用しています。サーバーがフィンガープリントの確認(yes/no)を求めてきた場合、スクリプトは「yes」と答え、次に「password」という行が表示されるまで待ち続けます。

Autoexpectの秘訣:コードを自動生成させる

スクリプトを手動で書くと、スペルミスが起きがちです。私はよくautoexpectを使って時間を80%節約しています。以下のコマンドを実行するだけです:

autoexpect ./your_script.sh

あとは、実際の人間と同じように操作してください。autoexpectがすべての操作を記録し、script.expというファイルに出力してくれます。あなたはそのファイルを開き、不要な部分を削除するだけで完了です。

セキュリティ上の注意:油断は禁物

ファイルにパスワードを保存するのは大きなリスクです。より安全に運用するために、私は常に以下の2つの鉄則を守っています:

  1. アクセス権限の制限:すぐにchmod 600 script.expを実行してください。あなただけがこのファイルを読み取れるようにします。
  2. 環境変数の活用: パスワードをハードコードしないでください。HashiCorp Vaultのようなシークレット管理システムや、暗号化された環境変数からパスワードを渡すようにしましょう。

Expectを導入して以来、私のバックアップ作業は100%自動化され、入力ミスによるエラー率はゼロになりました。もしあなたが「扱いにくい」システムの管理に頭を悩ませているなら、Expectこそが退屈な深夜作業からあなたを救い出す武器になるはずです。

Share: