この記事は CAMPHOR- Advent Calendar 2015 の2日目の記事です.
シェルで標準入力を読み取るコマンドといったらread
コマンドがあります.
read
の概要
read Man Page | Bash | SS64.com
read
コマンドはシェルの組み込みコマンドで 標準入力を読み取り 改行文字までまたはEOFまで読み込み、
引数に変数が指定されていた場合は入力値をその変数に格納します.
read
コマンドの区切り文字はシェル変数である$IFS
に格納されている文字が利用され、引数に複数の変数が指定されていた場合は指定された区切り文字で区切って変数に格納されます.(デフォルトでは$' \t\n' スペース・タブ・改行文字)
ちなみに入力の区切り文字は\
を頭につけることでエスケープすることができます.
便利っちゃ便利だけど\
がエスケープ文字として解釈されてしまうので不便だったりする(これは後述の-r
オプションで解決します)
read
オプション
上記のman page
を読めば分かりますがよく使うオプションとしては以下のようなものがあります.
-n<nchars>
- 普通は改行文字もしくはEOFを読むまで入力を読み取りますが、このオプションをつけることで
nchars
文字数だけ入力読み取ったらRET
入力を自動的に読み取り、入力待ちを終了させます.
- 普通は改行文字もしくはEOFを読むまで入力を読み取りますが、このオプションをつけることで
-r
- 上記で述べたように区切り文字は
\
でエスケープできますが-r
オプションをつけると、\
はエスケープ文字として機能しなくなりそのまま読み取られるようになります.
- 上記で述べたように区切り文字は
-s
- 通常は
read
コマンドで入力を読み取るとき ユーザーが入力した文字は画面に表示されますが-s
オプションをつけることで入力は表示されなくなります.パスワードの入力を要求するときとかに便利です.
- 通常は
-t<timeout>
IFS=
区切り文字に空文字を代入しています.これにより スペース・タブ・改行といった特殊文字が区切り文字として機能しなくなるため空白文字などをそのまま入力値として読み取ることができるようになります.
これで\
なんかで区切り文字をエスケープしなくてもそのまま受け取ることができるようになります.
注意 $IFS
の値をいじくるのはIFS= read xxx
という感じにコマンドの直前につけてコマンドと一緒に実行したり、書き換えたら後で元に戻すなりするようにしましょう. 環境変数書き換えることになるので大変なことになりそう.
一文字ずつ読み取って逐一処理する
RET
の入力なしに一文字入力を読み取って その入力値に沿って処理を行うというプログラムが書きたい場合、以下のような感じになります.
例えばキー入力でのカーソル移動、vimっぽくj,k,h,lの入力を読み取ってtput cuu1
とかでカーソルを移動させたいときに使います.
while IFS= read -r -n1 -s char; do case $char in j)...;; ... esac done
一文字しか入力しないのでIFS=
とか-r
は要らない気もしますがこんな感じでいけます.
特殊文字の検知
通常の文字は上記のようなやり方で入力を読み取ることができます. では特殊文字などはどうやって読みよればいいのだろうか...
man bash
を見ると以下のような記述が有ります. $'string'
のstring部分に以下に指定されているような文字を入力すると ANSI C standard
の文字に変換されるようです.
Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows: \a alert (bell) \b backspace \e an escape character \f form feed \n new line \r carriage return \t horizontal tab \v vertical tab \\ backslash \' single quote \nnn the eight-bit character whose value is the octal value nnn (one to three digits) \xHH the eight-bit character whose value is the hexadecimal value HH (one or two hex digits) \cx a control-x character
\xHH the eight-bit character whose value is the hexadecimal value HH (one or two hex digits)
とあるようにascii code
の16進数表記で特殊文字なんかも検知できそう.
(例えばa
のasciiコード16進数は61
なので$'x61'
でa
の入力を読み取ることができる.)
Extended ASCII in ANSI C
standard ASCII CODE はそもそも7ビットコードでありコード表を見てもCtrl+x
やArrow key
などは見当たりません.
あとで8ビット文字コードが主流になってきたとき 8bit を使って残りの128文字にみんな好き勝手な文字を当てはめていきASCIIを拡張してきたっぽい
そのようないろんな拡張ASCIIのうち有名なのがOEM Extended ASCII
と ANSI Extended ASCII
らしい
(参考: Ascii Codes - C++ Tutorials)
bashでは ANSI Extended ASCII のほうに変換されるっぽいですね.
Ctrl+x
などの入力
\cx a control-x character
とあるようにCtrl+x
などの入力は$'\cx'
などで読み取ることが出来ます.
せっかくなので先ほどの拡張ASCIIを使って読み取ってみると,Ctrl+x
のascii code
16進数表記は24なので$'\x24'
で読み取ることができます.
case $char in $'\cx'|$'\x24')...;; ... esac
矢印キー(Arrow key)の読み取り
矢印キーの ascii code
先ほどの拡張ASCIIコード表の上矢印(Up Arrow)をみてみると0;72
とあります.なんだこれ???
よくわからないので矢印キーの asciiコード16進数表記を調べてみましょう. 入力の16進数変換にはxxd
やhexdump
コマンドが便利
Arrow key
の入力値をread
で読み取りその入力値を16進数変換して表示してみます.
read key; echo "$key" | hexdump
このコマンドを入力したあとにUp Arrow(上矢印)
を入力すると1b 5b 41
というような結果が得られます.
どういうことかというとUp Arrow(上矢印)
の入力は1b 5b 41
という3つのascii code
の入力によって実現されてるっぽいです.
矢印キーの読み取り
case $char in $'\x1b\x5b\x41')...;; ... esac
という感じで読み取ることができる.
1文字ずつ読み取ってる場合はどうするんだよ!というのがありますが、僕は以下のような感じで一文字目が$'\x1n'(エスケープ文字)
だった場合はあと2文字くらい入力が起こってそうだから追加で2文字読み取って$char
に追加するみたいな感じでいきました.
while IFS= read -r -n1 -s char; do if [[ $char == $'\x1b' ]]; then read -r -n2 -s rest char+="$rest" fi
最後に
こんな感じのことを使ってニコニコ動画をターミナルから検索できるnicoterm
とかいうCLIクライアントを作りました.
使い方は簡単でコマンドの引数に検索クエリを入力するだけ オプションつければソート順も変更できるし カーソル位置でo
とかでサクッとブラウザで動画開けたりして便利
まとめ
今回bash
での解決策について記事を書きましたがbash
じゃなかったらどんな感じになるんだろう...
read
での読み取り以外でも役に立つことも多いかと思う...のでキー入力読み取りについてこの記事が助けになればという感じです.
この記事は CAMPHOR- Advent Calendar 2015 の2日目の記事です.
明日はyaitaimoです!