zshが無いと死ぬ人がbashでなんとかする

※この記事は、zsh Advent Calendar 2014 - Qiitaの19日目です。

zsh無しにbashで生きる

zsh大好きな皆さんは、bashを心の中のどこかでバカにしているのではないでしょうか。しかし最近のbashは、いろいろとzshを意識している(?)雰囲気があり、zsh特有だと思っていた機能が実はbashでも使えたりします。

そのため、

という人でも、bashがあれば多少の延命ができるかもしれません。本稿では、zshが無いというサバイバル環境下において、bashでなんとかすることをテーマにいくつかサンプルを挙げてみます。

なおここでは、bashのプロンプトを$、zshのプロンプトを%として表記します。zshの%は、「宇宙」を意味しています。zshは宇宙だからです。

bashの補完を強力にする - Programmable Completion

zshpingを打つときには、ホスト名を/etc/hostsに書いておけばTabキーで補完できます。

% ping ma[Tabキー]
% ping macmini      <嬉しい!>

一方bashでは、先頭に@が付いている場合のみTabキーによるホスト名の補完が効きます。そのためsshの時は良いのですが、pingでは補完されません。

先頭に@があれば、ホスト名として/etc/hostsから補完される:
$ ssh ozuma@ma[Tabキー]
$ ssh ozuma@macmini      <嬉しい!>

しかしpingの宛先は@が付かないので、補完されない:
$ ping ma[Tabキー]
$ ping ma             <Shit!>

「だからbashはダメだ」と鍛えられたzsh使いは思うことでしょう。しかし、実はbashでも、プログラム補完(Programmable Completion)という機能でzshと同様に補完できるのです。

プログラム補完を有効にする

プログラム補完は、機能自体はbashに組み込まれていますが、設定ファイルを別途用意する必要があります。

CentOS 6では、これら設定ファイルはbash-completionというパッケージに用意されています。ただしこのbash-completionパッケージはCentOS公式リポジトリには無く、EPELにしか用意されていません。そのため必要な場合にはEPELリポジトリから別途インストールする必要があります。

一方、CentOS 7ならば、公式のbaseリポジトリに用意されています。

$ yum info bash-completion
....省略....
利用可能なパッケージ
名前                : bash-completion
アーキテクチャー    : noarch
エポック            : 1
バージョン          : 2.1
リリース            : 6.el7
容量                : 85 k
リポジトリー        : base/7/x86_64
要約                : Programmable completion for Bash
URL                 : http://bash-completion.alioth.debian.org/
ライセンス          : GPLv2+
説明                : bash-completion is a collection of shell functions that take advantage
                    : of the programmable completion feature of bash.

CentOS 6/7のどちらにしろ、bash-completeパッケージをyumコマンドでインストールするだけで準備完了です。

# yum install bash-complete
現在の補完設定を確認する

bashのビルトインコマンドであるcompleteを引数無しで実行すると、現在設定されている補完リストが表示されます。

$ complete
complete -F _kill kill
complete -F _renice renice
complete -o default -F _service tomcat
complete -F _smbpasswd smbpasswd
complete -F _pgrep pidof
complete -F _postconf postconf
complete -F _update_alternatives alternatives
......

例えば、pingコマンドに対しては以下のように定義されています。

complete -F _known_hosts ping

プログラム補完は、ping以外にも様々なコマンドに対応しています。例えばsysctlのクソ長い記述も、Tabで補完できます。TCPによるSYN Flood攻撃の際に重要となる、net.ipv4.tcp_synack_retriesを補完してみましょう。

$ sysctl net.ipv4.tcp_syna[Tabキー]
$ sysctl net.ipv4.tcp_synack_retries      <嬉しい!>
net.ipv4.tcp_synack_retries = 5

またopensslコマンドのような、増築に増築を重ね続けてそびえ立つクソ複雑なコマンドも、うろ覚えで利用できます。

$ openssl x[Tabキー]
$ openssl x509 -[Tabキー][Tabキー]
-C               -clrtrust        -issuer          -req
-CA              -dates           -issuer_hash     -serial
-CAcreateserial  -days            -keyform         -set_serial
-CAform          -email           -md2             -setalias
.........

他にも、そんなもん誰が使うんだって感じですが、iptablesのチェイン名なども補完できます。

# iptables -A I[Tabキー]
# iptables -A INPUT    <別に嬉しくない!>

興味のある方は、/etc/bash_completion.dの中を覗いてみましょう。プログラム補完の設定自体はシェルスクリプトで書かれているので、なんとなくオーラで分かると思います。

パス名展開で「**」を使えるようにする - globstar

zshでよく使われるのが、**というパス名展開です。あるディレクトリ配下のファイルを根こそぎ相手にしたいときに役に立ちます。

% wc -l **/*.java

これはbashでも、shoptコマンドでglobstarというオプションを設定してやることで利用できます。

$ shopt -s globstar
$ wc -l **/*.java

これで、「書いたJavaソースファイルの行数(だけ)で進捗を測る」というクソ環境でも安心です。コメント行とimport文をモリモリ増やしましょう。

cdコマンドを省略する - autocd

zshユーザは忙しいので(もちろん~/.zshrcのカスタマイズに忙しいのです)、「cd」の2文字すら打つのを嫌がります。そのため、cdコマンドを省略できるauto_cdを設定している人が多くいます。

% setopt auto_cd
% pwd
/
% bin
% pwd
/bin

これは、忙しいbashユーザ(もちろん~/.bashrcのカスタマイズに忙しいのです)も利用できます。zshと同様に、autocdというオプションをshoptコマンドで設定できます。

$ pwd
/
$ shopt -s autocd
$ bin
cd bin
$ pwd
/bin

bashの場合は展開された「cd bin」を表示してくれるので、zshよりも若干フレンドリーです。

大文字小文字を無視して補完する

zshユーザは余計な暗記をしたくないので(zshの機能を覚えたいので)、ファイル名の記憶が常におぼろげで曖昧です。そのため、大文字小文字を無視して補完してくれる以下のような設定を、~/.zshrcに書いておくことが好まれます。これで大文字小文字を無視して、Tabキーで補完できます。

zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'

一方bashでは、事情がちょっぴり複雑です。現在のbashには、zshと同様に大文字小文字を無視して補完させる明示的なオプションはありません。ただし、~/.inputrcに以下のように書いておくことで、bashでも大文字小文字を無視してTabキーで補完することができます。

set completion-ignore-case on

しかし、~/.inputrcはreadlineの設定ファイルであり、bashのみから使われるわけではないので、若干気持ち悪いです。副作用に気をつけましょう。

追記:~/.inputrc のカスタマイズをbashにのみ効かせる

コメントで id:xxmagai さんから教えてもらいましたが、次のように~/.inputrcファイル内でif文を使うことで、設定をbashのみに限定できます。

$if Bash
set completion-ignore-case on
$endif

詳しくはinfo readlineして、「1.3.2 Conditional Init Constructs」辺りを読みましょう。

前述のように、~/.inputrc にそのまま書くとreadlineを使うコマンド全てに影響があるので、例えばftpでも大文字小文字補完が効くようになります。だからと言って困ることはあまりないでしょうが、やはりif文を使ってbash限定にしておいた方が好ましいでしょう。

nocaseglobはちょっと違う

bashのnocaseglobというオプションをshoptコマンドで設定すると、大文字小文字を無視してパス名展開されるようになります。ただしこれは、Tabキー補完はできません。この点、時々勘違いして書かれている文書があるので注意が必要です。

$ shopt -s nocaseglob

$ ls
RUNNING.txt
$ less run[Tabキー]        <補完されない!>

$ ls running.*
RUNNING.txt          <大文字小文字無視してパス名展開された!>

このようにnocaseglobは、あくまでパス名展開のみの機能です。

終わりに

ここで紹介した以外にも、最近のbashには様々な機能が追加されています。全身から血を吹き出して死ぬ前に、bashのmanを熟読してみると良いかもしれません。

ちなみに私はzshの補完のヌルヌル感が苦手で、zshはあまり使っていません。

最後に宣伝

6月にシェルスクリプトの本を出しましたが、このたびKindle版も出ました。興味のある方はポチっとして頂けると、私も全身から血を吹き出さずに済みます。