Tcl/Expect でシェルログインを自動化する


こんにちは!デバイスソフトウェア開発部の本間です。

弊社ではデータロガーや通信型ドライブレコーダーなど、様々な IoT デバイスを開発・販売しておりますが、これまでに多くのデバイスの FW 開発・保守に携わらせて頂きました。

FW を開発・保守する際には、プログラムパッチやログ解析の為に、デバイス毎に異なる手順でデバイス内部へアクセス(シェルログイン)する必要があります。また、アクセスする経路もイーサネット、 LTE 回線、RS-232C、UART など多岐にわたります。その為、デバイス毎に接続手順やログイン情報(鍵)などを適切に管理することが重要です。また、作業効率化のために、接続・ログインの一連の流れを自動化することは欠かせません。

私は、デバイスへの接続・ログインを自動化するツールとして、Tera Term というターミナルエミュレータを使用しています。日本のシステム開発現場ではよく使われるツールではないでしょうか。しかし、Tera Term は Windows 専用のソフトウェアで Windows 以外の環境では使用できないという課題があります。

そこで今回は、Tera Term の代替として、かつクロスプラットフォームで動作するツールである Tcl/Expect を紹介させて頂きます。Tcl/Expect というツールを使用することで、Tera Termのようにターゲットへの接続・ログインを自動化できます。

Tera Term について

Tera Term は Windows 向けのターミナルエミュレータです。ターミナルエミュレータとしての機能以外に、SSH クライアント、Telnet クライアント、シリアル通信クライアントを内蔵しており、ネットワークやシリアル通信を介してターゲットへ端末接続&ログインすることが可能です。SCP や XMODEM によるファイル転送を GUI で簡単に実行することもできます。

中でも強力な機能が Tera Term マクロです。マクロ言語 “Tera Term Language (TTL)” を記述することで、ターミナルの起動・制御、SSH やシリアルによる接続・ログインなどの一連の作業を自動化することができます。Tera Term マクロのマニュアルはこちらにあります。

Tcl/Expect とは?

Tcl/Expect とは、対話型 CUI プログラムとのやり取りを自動化するためのツールです。Tcl というスクリプト言語を拡張したものであり、Tcl の文法で対話スクリプトを記述します。マイナーな言語と思うかもしれませんが、他に Tcl/Tk という歴史のある有名な GUI ツールキットがあり、短いコードで簡単に GUI を作成できます。Tcl は Lisp を彷彿とさせる特殊な言語ですが、Expect や Tk という強力なライブラリの存在から、今でも学ぶ価値はあるのではないでしょうか。

Tcl/Expect を含むこれらのツールは歴史があり枯れたソフトウェアである為、様々な環境に移植されておりクロスプラットフォームで使用できます。

なお、Tcl 拡張であることを強調するために “Tcl/Expect” と表記していましたが、Expect が正式名称であるため、以降は “Expect” と表記します。

Expect で自動化してみる

Expect で接続・ログインを自動化する環境を準備します。Expect 以外にターミナルエミュレータや各種通信クライアントのインストールが必要です。具体的なソフトウェアの例を以下に挙げました。これらのソフトウェアは、cygwin (msys) や macOS,  各種 Linux ディストロなど、様々な環境でパッケージとして利用できると思います。

種別 ソフトウェア名
ターミナルエミュレータ mintty, iTerm, Xterm, URxvt
SSH クライアント OpenSSH
Telnet クライアント netkit-telnet, inetutils-telnet
シリアル通信クライアント cu, picocom ※

※ minicom や screen でもシリアル通信できますが、これらは curses による TUI を出力する為、行指向の Expect と相性が悪いです。

SSH 接続を自動化する

それでは Expect で SSH 接続を自動化してみます。

以下は localhost で起動している WSL2 (Ubuntu 24) へ SSH 接続する Tera Term マクロです。

テストユーザでシェルへログインした後、root へ昇格しています。まず、connect コマンドでターミナルエミュレータ起動し、SSH 接続・ログインを実行します。続いて、wait コマンドでシェルプロンプトやパスワードプロンプトの文字列表示を待機し、sendln コマンドでコマンド実行やパスワード入力のための文字列を送信します。

この Tera Term マクロを Expect スクリプトで書き直してみましょう。以下が書き直した後のスクリプトです。

Expect と対話させたいコマンドは spawn コマンドで起動します。ここでは ssh 接続を自動化するため、ssh コマンドを spawn で起動します。

パスワード入力の為、ssh コマンドが出力する特定の文字列を待機する必要がありますが、これは expect コマンドで処理します。-re オプションを付けることで正規表現によるパターンマッチも可能です。接続先が初めて接続するホストの場合は、公開鍵確認のプロンプト(yes/no)が表示されるため、この場合は send コマンドで yes を入力し、exp_continue コマンドで待機を継続します。

Expect による対話が完了しログインできた後は、interact コマンドで ssh コマンドの入出力制御を Expect からユーザへ切り替えます。

それでは実演してみましょう。上記スクリプトを ssh-login.tcl として保存して実行権限を付け、Xterm からそのスクリプトを実行してみます。

無事 SSH ログインし root ユーザに切り替えることができました。スクリプト実行中は一切キー入力していません。ssh コマンド実行から公開鍵確認とパスワード入力、root 昇格までの一連の流れを自動化することができました。

シリアル接続を自動化する

次にシリアル接続を自動化してみます。

下記は COM3 ポートへシリアル接続されたターゲットへログインする Tera Term マクロです。

テストユーザでシェルへログインした後、root へ昇格しています。connect コマンドでは、適切なシリアル通信パラメータを設定してポートへ接続します。また SSH 接続とは違い、場合によっては接続後にキー入力によるログインが必要になります(下記では getty に対してログイン処理している)

この Tera Term マクロを Expect スクリプトで書き直してみましょう。下記が書き直した後のスクリプトです。

ssh 接続時と違う点としては、spawn コマンドで起動するプログラムです。ここではシリアル接続を自動化するため、cu コマンドを spawn で起動します。また、シリアル通信の動作は事前に stty コマンドで設定しています。

それでは実演してみましょう。上記スクリプトを serial-login.tcl として保存して実行権限を付け、Xterm からそのスクリプトを実行してみます。

無事にシリアル接続して root ユーザに切り替えることができました。スクリプト実行中は一切キー入力していません。シリアル接続時においても、一連の流れを自動化することができました。

応答待ちタイムアウトを設定する

何かしらの理由でターゲットが応答せず、接続・ログインできない状況はよくあります。そのため、ターゲットからの応答を待つタイマーを設定し、タイムアウトした場合は接続失敗として適切な措置(スクリプトの終了など)を実行する必要があります。

下記は Tera Term マクロにおける応答待ちタイムアウトの設定例です。

Expect でも、下記のように対話コマンドの応答待ちにタイムアウトを設定することができます。

作業ログを記録する

試験や本番環境のメンテナンス時に、エビデンスとしてターゲットやリモートの作業履歴を記録する必要があったことはないでしょうか?
もしそんなケースに遭遇した場合は、Tera Term のログ記録機能が役に立つかもしれません。Tera Term では端末接続後の出力(リモートから受信した文字列)をログ保存でき、作業履歴として記録できます。以下はそれをマクロで実行する例です。

Expect でも、スクリプト実行中の出力をログ保存することができ、端末接続後の操作を記録することができます。

おわりに

今回は Expect を用いたシェルログインの自動化について紹介しました。紹介した機能や事例はほんの一部にすぎません。コマンド引数やユーザー入力を受け付けられるようにすれば、より汎用的な自動ログインスクリプトを作成できます。また、Tk を組み込むことでスクリプトの入出力を GUI 化することも可能です。ログイン処理に限らず、テストや運用管理などの作業も自動化して、日々の業務の効率化を進めていきましょう!

エコモットでは一緒にモノづくりをしていく仲間を随時募集しています。弊社に少しでも興味がある方はぜひ下記の採用ページをご覧ください!