rcmdnk's blog
Last update

覚えて便利 いますぐ使える!シェルスクリプトシンプルレシピ54

trashコマンドを拡張 している時に、選択画面で上下するときにVimみたくCtrl-F で1画面進む、みたいなことしたいな、と思って Controlキーを含んだ入力をreadで読み取って扱う方法を取り入れたのでそのメモ。

入力キー1つを受け取る

入力キーを1つ受け取るにはBashでは

read -s -n 1 c

Zshでは

read -s -k 1 c

とします。 スクリプトがこの時点まで進んだ時に入力待ちになって、 何か1つでもキーを押すと、そのキーがcに入ります。

必要なら直前に

echo -n "input: "

とでも出しておけばわかりやすかったり。

後は何でもいいのでcを見てスクリプトを進めていきます。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env bash
echo -n "input: "
read -s -n 1 c
case $c in
  a )
     echo "input is a"
     ;;
  * )
     echo "input is other"
     ;;
esac

Controlと同時押しのキーを受け取る

通常の文字列ならばaとかで良いわけですが、 Ctrl-Aみたいな場合には、それによって送られるASCIIコードで 直接判断します。

Bashのmanを見てみるとQUOTINGの部分に $'string'と書くと、ANSI C standardに変換される、とあります1

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

(man bashより引用)

これを使えば、 各キーとControlキーの組み合わせのASCIIコードが分かればそれと比べて 判定することが出来ます。

追記: 2014/07/30

上のmanをここまで引いておいて馬鹿な話ですが、 最後の行に\cxcontrol-x、と書いてあります。 なので、わざわざキーコードなんて調べなくても \caCtrl-aが得られます。

他のANSI Cのコードなどはここ参照:

ANSI.SYS Key Codes – Technical Notes

追記ここまで

コードは16進数では0x00がCtrl-@ (NULL)に割り当てられていて、 0x01から0x1aまでの26個がCtrl-a~zになります。

追記: 2014/09/22

Ctrl-Spaceは通常Ctrl-@と同じ NULLに割り当てられています。

なので上のエスケープの例でも$'\c@'$\c 'は同じとみなされます。

Control character - Wikipedia

追記ここまで

数えるのが面倒なら

echo -n ^A|xxd

とか

echo -n ^A|od -x

とかで調べられます(^AはCtrl-vCtrl-aで出力)。

これを使って

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bash
echo -n "input: "
read -s -n 1 c
case $c in
  a )
     echo "input is a"
     ;;
  $'\x01' )
     echo "input is Ctrl-a"
     ;;
  * )
     echo "input is other"
     ;;
esac

みたいにControlキーを受けて判断する事が出来る様になります。

Tips

8進数または16進数で書くときの注意

コードは上のmanにあるように8進数でも16進数でも書けるわけですが、 書式にしたがって8進数なら

\1
\01
\001

はOK。上にあるように4つ以上数字を入れると正しく判断してくれません。

また16進数なら

\x1
\x01

はOK。3文字以上つかって\x001や普段16進数を書く時みたいに\0x01みたいに書くと正しく 判断してくれないので、特に16進数の時は注意です(自分が軽くハマっただけですが。。。)。

Ctrl-Cを使いたい場合

Ctrl-Cは終了シグナルを送るので直接使えません。

ちょっと使い方が変わりますが、

trap "echo input is Ctrl-C" 2

の様にtrapで引っ掛けて上げることなら出来ます。

水平タブ(Ctrl-I)等を使いたい場合

水平タブ(\tCtrl-I、0x09)は区切り文字となってしまって これを入れると何も入力されてない、と判断されますが、これを使いたい場合は

orig_ifs=$IFS
IFS=
read -s -n 1 c
IFS=$orig_ifs

の様にIFSを空白だけにして、タブを区切り文字から外してあげれば使える様になります。

使えない文字

上の様にIFSをなんとかしても Ctrl-J/M(改行、復帰)は何も送られないまま キーが閉じられたとみなされてNULL(0x00)と判断されます。

Enterをいきなり押しても同じ。

また、Ctrl-Y/Z(媒体終端/置換)はスクリプトの上から 中断信号を送るので捉えることが出来ません。

この辺りは直接ターミナルの設定を無理やり弄らないと使えません。

Ctrl-SなんかもそのままだとXOFFに割り当てられてて 表示の中断になってしまって使えません2

ESCを判断する

ASCIIコードが使えるのでESC(Ctrl-[)も判断できます。 ESC$'\x1bまたは$'\e' で判定できます。

まとめ

この$の後にシングルクォートを使って$'~'みたいな形で書くのは 昔、たまにみては'$~'の書き間違えか?とか勝手に思ってたんですが、 書いてある通りに書かないと動かないコードがあって困ったことがあります。 googleに聞くにもなんと聞いたら良いのかが難しいので。 まずman見てみろというのが身にしみる感じですが、 manでも検索するにもある程度知らないと探しようがない感じなので。 (Bashくらいのmanは一度きちんと全部読んで理解しておかないといけないな。。。 というだけですが)

Sponsored Links
  1. $の後をシングルクォートで囲うことでコードを送ることが出来るようになります。 echoなんかでも

    $ echo aaa$'\n'aaa
    aaa
    aaa
    $
    

    みたいに使えます。 ちなみに\nは16進数だと0A(Ctrl-J)に割り当てられてるので

    $ echo aaa$'\x0a'aaa
    aaa
    aaa
    $
    

    としても一緒。

  2. Ctrl-Sはコマンド履歴のインクリメンタルサーチ で使いたいのでOFFにしてる人が多いと思いますが。

    ターミナルキーバインドの設定等

Sponsored Links

« コマンドラインで使うゴミ箱コマンド シェルスクリプトでif文をANDで書き換える時の注意 »

}