CUIでシェルスクリプトや設定コマンドを書いていると、自分以外の誰かが使う可能性を考えて、よりユーザーフレンドリーなインターフェースにしたいケースが稀にあります。今回はそんなケースで使える、「whiptail」を紹介します。
whiptailとは?
whiptailは「newt」と呼ばれるテキストモード(CUI)用のウィンドウツールキットに同梱されているプログラムです[1]。具体的にはDebianパッケージの設定システムであるdebconfや、Xが動いていない環境でim-configが設定ダイアログを表示するときに使用しています。
このwhiptailをシェルスクリプトと組み合わせて使うと、CUI上でも簡単に問い合わせダイアログや選択用のチェックボックス、進捗バーの表示を作れることができるのです。しかも、whiptailパッケージはdebconfが依存している都合上、どのようなUbuntuマシンでも最初からインストールされています。よって環境依存性をあまり考慮しなくても使えるというメリットも存在します。
whiptailの使い方
whiptailはただのコマンドです。whiptailコマンドを実行したら、戻り値や標準出力・標準エラー出力に表示される値を元に次の処理を行うようにシェルスクリプトを書くだけです。シェルスクリプトを書いたことがある人であれば、あとはmanページを読めば、やりたいことを実現できるでしょう。
そこでこのRecipeでは「どんなことができるの?」という観点で、すべての機能をスクリーンショット付きでお届けします。
whiptailの基本的な書式
whiptailの基本的な書式は以下のとおりです。
レイアウトオプションにはボックスタイトルやボックスの位置といった、ボックス共通のレイアウトに関するオプションを設定します。たとえば「--backtitle」は画面左上に文字列を表示し、「--topleft」はダイアログを画面中央ではなく左上隅に表示します。
ボックスオプションにはボックスの種類や種類ごとのオプションについて設定します。たとえばチェックボックスなら選択肢の一覧を指定しますし、入力ボックスなら初期値を設定します。
ちなみにlibnewtはgettextによる翻訳をサポートしています。その都合上、「Ok/Cancel/Yes/No」は日本語ロケールならすべて「了解/取消/はい/いいえ」と翻訳されます。今回の記事でOKボタンやYesボタンと言及していても、ボタンのラベルはロケールによって異なることがあるので注意してください。
Yes/Noダイアログ
Yes/Noダイアログはその名のとおり、Yes/Noボタンを表示するダイアログです。
高さと幅は数値で指定します。説明に指定した文字列が幅を超えると自動的に改行します。両方とも0にすると、適切な高さと幅を自動的に設定します。
たとえば次のコマンドは、図1のキャンセルダイアログを表示するコマンドです。
「--title」はダイアログのタイトルになります。「--no-button」はNoボタンのラベルを変更するオプションです。「--yes-button」オプションを使えば「はい」も変更できますし、「--cance-button」や「--ok-button」オプションも存在します。
メッセージボックス
メッセージボックスは、メッセージとOKボタンのみを表示するダイアログです。ユーザーに何か情報を通知し、ユーザーがそれを確認するまで待つ場合に使用します。
上記のように文字列の中に「\n
」によって改行文字を入れることも可能です。ただしANSIエスケープコードなどは埋め込めません。
インフォボックス
インフォボックスはメッセージボックスと似ていますが、ユーザーの操作を待たずに即座に終了します。表示されたメッセージは画面に残るので、ノンインタラクティブなシェルスクリプトで通知だけ残したい場合に便利です。
ただしxtermやGNOME端末では問答無用でメッセージがクリアされるため、そのままでは使えません。もしこれらの端末ソフトウェア上で使用したい場合は、次のように環境変数TERMにxterm系以外を設定しましょう。
入力ボックス
入力ボックスは、ユーザーが自由に入力できるダイアログを表示します。
入力した内容は「標準エラー出力」に表示されます。コマンド置換などで結果を取得したい場合は、後述の「メニューボックス」の項目を参照してください。キャンセルボタンを押した場合は、戻り値が1となり標準エラー出力には何も表示されません。
入力ボックスの場合は、高さと幅の数字の後ろに入力ダイアログの「初期値」を指定できます。
パスワードボックス
パスワードボックスも入力ボックスと同様にユーザーの入力を受け付けます。パスワードボックスの場合、入力された文字が「*
」でマスクされます。
当たり前のことではありますが、マスクされるのは表示部分だけです。入力した文字列は、入力ボックスと同様に標準エラー出力にマスクされずに表示されます。
ちなみに入力ボックスと同様に初期値を指定できます。ただしマスクされているため、ユーザーは初期値が何であるかはわかりません。さらにシェルコマンドとして記述する都合上、初期値はマスクされずにスクリプトやコマンド履歴に残ることになります。よってパスワードボックスで初期値を使うことはまずないでしょう。
テキストボックス
テキストボックスは任意のファイルの内容を表示するダイアログです。ファイル名を指定すれば、それを表示します。またファイル名に/dev/stdinを指定すれば、ヒアドキュメント経由でコマンドの結果を渡せます。
ボックスの幅と高さを指定した場合は、その領域に入るサイズでしかファイルを表示しません。幅と高さを指定しなかった場合は、ボックスのサイズをあふれる時に、カーソルキーやPageUp/PageDownキーでスクロールできます。幅と高さを指定しつつスクロールしたい場合は「--scrolltext」オプションを使用してください。
メニューボックス
メニューボックスはその名のとおり、選択式のメニューを表示するダイアログです。ボックスの高さと幅の後ろに、メニュー領域の高さを指定した上で、「タグ 項目」の文字列をメニューの数だけ繰り返してしていきます。
選択肢を選んだ上でEnterキーを押すか、OKボタンを押すと標準エラー出力に「タグ」の方が出力されます。Cancelボタンを押すと戻り値が1となり、標準エラー出力には何も表示されません。
シェルスクリプト内部でwhiptailを使う場合、その選択結果をコマンド置換を用いて変数に代入したいことがあるでしょう。単純に標準エラー出力を標準出力にリダイレクトする方法だと、whiptailのUIまで変数に入ってしまいます。そこで以下のコマンド(3>&1 1>&2 2>&3
)では、標準出力と標準エラー出力を入れ替えています。
この方法は、入力ボックスやパスワードボックス、チェックリスト、ラジオボタンなどでも使用できます。なお「--output-fd」オプションを使えば、出力先のファイルディスクリプタを直接指定できます。
上記コマンドでは高さと幅だけでなく、メニューの高さも0に設定しているため、メニューが収まる適切なサイズのボックスが作成されます。
「--notags」を使うと、タグを非表示にできます。どれを選んでも結果的に「綾鷹」にしたい場合に便利です。
チェックリスト
チェックリストは複数選択可能な選択ボックスです。メニューボックスが一つを選択すれば終了なのに対して、チェックリストではどれを選ぶか、選ばないかを試行錯誤した上で、確定できます。
チェックリストではタグと項目に加えて、初期状態を示すフラグも指定します。OFFならチェックが外れている状態で、ONならチェックがついています。大文字小文字は区別しません。なお、項目については上記のように「""
」と指定することで省略できます。「--noitem」オプションを使うと、「""
」すら不要になります。
標準エラー出力には選択したタグのリストが出力されます。たとえば上記のコマンドで「きのこの山」「バームロール」「ホワイトロリータ」を選択すると、結果的に以下のような出力になります。
シェルスクリプト側はこれを受け取って、何が選択されたかを判断します。
ラジオボタン
ラジオボタンは一つしか選択できないチェックリストです。コマンドの使い方はほぼ同じです。ユーザーはどれか一つを選択すると、他の選択項目がOFFになります。
進捗バー
進捗バーは進捗状態にあわせてアニメーションするバーです。また、バーの内側にはパーセンテージの表示も行われます。
アニメーションをさせるためには、継続的に標準入力から進捗状態のパーセンテージを通知する必要があります。さらに数字ではなく「XXXnパーセンテージn文字列nXXX」という文字列を送ると、パーセンテージの更新とともに、パーセンテージの次の行の文字列が、ダイアログの中の文字列で置き換わります。
このあたりは若干ややこしいので実際にやってみると良いでしょう。
whiptailのオプションそのものはシンプルです。最後の数字はパーセンテージの初期値です。
上記をシェルスクリプトとして書き直すと次のようになります。