読者です 読者をやめる 読者になる 読者になる

たにしきんぐダム

プログラミングやったりアニメやゲーム見たり京都に住んだりしてます

readコマンドで 矢印キー や Ctrl+x などの入力を読み取る

shell

この記事は 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入力を自動的に読み取り、入力待ちを終了させます.
  • -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+xArrow keyなどは見当たりません.

あとで8ビット文字コードが主流になってきたとき 8bit を使って残りの128文字にみんな好き勝手な文字を当てはめていきASCIIを拡張してきたっぽい

そのようないろんな拡張ASCIIのうち有名なのがOEM Extended ASCIIANSI Extended ASCII らしい

(参考: Ascii Codes - C++ Tutorials)

bashでは ANSI Extended ASCII のほうに変換されるっぽいですね.

Ctrl+xなどの入力

\cx a control-x character とあるようにCtrl+x などの入力は$'\cx'などで読み取ることが出来ます.

せっかくなので先ほどの拡張ASCIIを使って読み取ってみると,Ctrl+xascii code 16進数表記は24なので$'\x24'で読み取ることができます.

  case $char in
    $'\cx'|$'\x24')...;;
    ...
  esac

矢印キー(Arrow key)の読み取り

矢印キーの ascii code

先ほどの拡張ASCIIコード表の上矢印(Up Arrow)をみてみると0;72とあります.なんだこれ???

よくわからないので矢印キーの asciiコード16進数表記を調べてみましょう. 入力の16進数変換にはxxdhexdumpコマンドが便利

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とかでサクッとブラウザで動画開けたりして便利

github.com

https://gyazo.com/8334764edfb2c1ec9e9beca21d64b2af

まとめ

今回bashでの解決策について記事を書きましたがbashじゃなかったらどんな感じになるんだろう...

readでの読み取り以外でも役に立つことも多いかと思う...のでキー入力読み取りについてこの記事が助けになればという感じです.

この記事は CAMPHOR- Advent Calendar 2015 の2日目の記事です.

明日はyaitaimoです!

参考