shell のちょっとしたテクニック

後輩が

cat README | tr ' ' '\n' | sort | uniq -c | sort -nr | head

てなテクニックを見て、びっくりしたみたいな話をしていたのだが、こういうパイプラインを利用するテクニックを学んでいないのは色々損な気がする。
ていうか、サーバで丸一日以上かかるような処理を実行するのもしょっちゅうなのに、GNU screen も nohup も知らないってのはいろいろ支障があるような気もするのだが、だれも教えないものかなぁ。
ということで、bash or zsh のちょっとしたテクニックとか*1。リダイレクトとかパイプラインは略。

連続実行

単純に連続実行。

% foo; bar

foo が正常終了したときだけ bar を実行

% foo && bar

foo が正常終了しなかったときだけ bar を実行

% foo || bar

&&||は本来は論理演算子だが、C と同じく真偽が決定した時点でそれ以上評価(実行)しない。
configure と make を && で結合するのは定番。

% ./configure && make

job control

コマンドの末尾に & をつけるとバックグランドで実行されるというのは結構知られているのだが、C-z で一時停止して、さらに bg を実行するとフォアグラウンドジョブ をバックグラウンドに出来るのは案外知らない人が多い気がする。

% find / > /dev/null 2>&1
(C-z)

zsh: suspended  find / > /dev/null 2>&1
% jobs
[1]  + suspended  find / > /dev/null 2>&1
% bg
[1]  - continued  find / > /dev/null 2>&1
% jobs
[1]  + running    find / > /dev/null 2>&1
% fg
[1]  + running    find / > /dev/null 2>&1

jobs で実行中の job 一覧、bg で停止した job をバックグラウンド実行。fg で停止中もしくはバックグランド実行中の処理を再度フォアグラウンドで実行できる。
[1] と出ているのは job 番号で、ジョブが複数ある場合に fg %1 とか bg %2 といった具合に job を指定するのに使用する。
今実行中のコマンドに続けて別のコマンドを実行したくなったりした場合、

% foo
(C-z)
% fg && bar

なんてことも出来る。

コマンド置換

`command` か $(command) でコマンドの実行結果がコマンドライン中に展開される。
たとえばカレントディレクトリ以下(サブディレクトリ含む)の "*.c" から strcmp を探したければ

grep -H strcmp $(find . -type f -name "*.c")

などとする*2

プロセス置換

<(command) は command の標準出力をあらわす名前付きパイプのファイル名に置換される。要するに

% foo > foo.out
% bar > bar.out
% diff -u foo.out bar.out

とする代わりに

% diff -u <(foo) <(bar)

と出来る。ただし、あくまでパイプなので seek できないことに注意。zsh なら =(command) という記法もあり、こちらは seek もできる。

履歴

カーソルキーを使った履歴参照は大抵皆知っているのだが、「!」を使った履歴参照は案外知らない人が多い。

!!
一つ前のコマンドを実行
!-n
n行前のコマンドを実行
!string
stringから始まるコマンドのうち、もっとも最近実行したコマンドを実行する

あと、Ctrl + r で後方に向かって履歴をインクリメンタル検索が出来たりして、これも結構便利。
尚、履歴は出来るだけ保存するべし。zsh なら .zshrc にこんなのを書いておく。

HISTFILE=$HOME/.zsh-history
HISTSIZE=100000
SAVEHIST=100000

GNU screen とか nohup とか

SSH などでログインしたマシンで時間のかかる処理を行う場合は GNU screen で pty を切り離せるようにするか、SSH の session が切れても大丈夫なように nohup を使う。さもないと、何かの拍子に SSH の session が切れた場合にプロセスに SIGHUP が送られ途中でプロセスが止まってしまう。

*1:csh とか tcsh は知らない

*2:より汎用的には find . -type f -name "*.c" -print0 | xargs -0 -e grep strcmp -H