auientの日常

ノンジャンルで書きたいことを書くブログ

別に火は吹かないid:auientによるshell tips

id:naoyaさん、id:wadapさんのshell tipsの記事が話題になっていたので、便乗して自分もtipsを書いてみようと思います。

シェル操作編

$_で直前の引数を参照する

[nt@localhost] $_で一つ前のコマンドの最後の引数を参照できます。argv[argc-1]になるようです。これはクソ長い名前のディレクトリを作ってcdするときなどに便利です。

[nt@localhost] $ mkdir -p very/long/path/or/confusing_name_directory/20140426
[nt@localhost] $ cd $_
[nt@localhost] $ pwd
/home/nt/very/long/path/or/confusing_name_directory/20140426
[nt@localhost] $ 

irbや確かpythonでも _ で最後の戻り値を参照できますね。いつからある機能なのか知りませんが、ここら辺から来てる慣習のような気がします。

!!で直前のコマンドを実行する

!!が一つ前のコマンドに展開されます。別にヒストリーを1つ戻っても同じですが、同じコマンドをsudoするときにドヤ顔できます。

[nt@localhost] $ service httpd restart
Error: Permission denied
[nt@localhost] $ sudo !!
sudo service httpd restart
[nt@localhost] $

これは古いシェルにはない機能かも。手元のbashtcshにはあり、kshにはなかったので。

Ctrl-z、jobs、fg、bgでジョブを自在に操る

Ctrl-zで現在のプロセスを中断できる、というのが紹介されていました。ならばjobsとfgとbgもセットで覚えてしまいましょう。

[nt@localhost] $ vi foo.rb

# viの中でCtrl-zを押すとシェルに戻れる

[1]+  Stopped                 vim foo.rb
[nt@localhost] $ 
[nt@localhost] $ tail -f php_error_log 

# tail -f でも同じ

^Z
[2]+  Stopped                 tail -f php_error_log
[nt@localhost] $ 
[nt@localhost] $ make -f large_program.mak 

# 長ーいコンパイル中もOK

^Z
[3]+  Stopped                 make -f large_program.mak
[nt@localhost] $ 

確認するにはjobsコマンドを使います。これで現在のジョブが一覧表示されます。

[nt@localhost] $ jobs
[1]   Stopped                 vim foo.rb
[2]-  Stopped                 tail -f php_error_log
[3]+  Stopped                 make -f large_program.mak
[nt@localhost] $ 

元に戻るにはfgコマンド(fore ground)を使います。引数なしで直前のジョブ、fg - で一つ前のジョブに戻れます(cdと同じ!)。fg %1 と番号指定することで任意のものに戻れます。[1][2][3]と表示されているのがそれですね。ジョブ番号といいます。

元のジョブを続けたいけど入力は必要なくて待つだけ、というときにはbg (back ground) を使います。引数のルールはfgと同じ。これはコマンドを&つきで実行したときと同じ状態(バックグランド実行)になります。で、元のジョブを終了したいときはkill %1でOKです。

# makeはバックグランド実行したい
[nt@localhost] $ bg
[3]+ make -f large_program.mak &

# tailはもう見ないので終了
[nt@localhost] $ kill %2
[2]+  Terminated: 15          tail -f php_error_log

# viに戻る
[nt@localhost] $ fg
vim foo.rb
set -o vi

bashのデフォルトのキーバインディングemacs風(Ctrl-aで行頭、Ctrl-eで行末など)になってますが、これはたまたまそうなってるだけで、set -o viでviふうになるよ!escでコマンドモード、hjklで移動のおなじみのやつです。元に戻すにはset -o emacsとします。
ただこれはあまりオススメできなくて、自分はvi派だけどシェルはemacsキーバインディングにしています。コマンドみたいに短い入力だとモードの切り替えが面倒なんだよね。まあ慣れか。

シェルプログラミング編

sh -n, sh -xでデバッグする

sh -n script.shとすると構文チェック、sh -x script.shとすると実行コマンドを標準エラーに出しながら実行。シェルスクリプトの開発時には便利です。

set -u, set -eで堅牢なスクリプトを書く

スクリプト内でset -uとすると、未定義の変数展開をエラーにしてくれます。set -eとすると、コマンドがエラーになった時点でスクリプトの実行を中断してくれます*1
私に言わせれば、これを使っていないシェルスクリプトはuse strict; use warnings;していないperlプログラム、ErrorExceptionを使っていないphpプログラムのようなものです。使い捨てにするスクリプトならともかく、お金をもらって書くスクリプトには是非書きましょう。

{}でコマンドをグループ化する
echo "メッセージ" >> logfile.txt
ls >> logfile.txt
別のコマンド >> logfile.txt

こんなスクリプトをときどき見ます。毎回ファイルを開いていてダサいですね。ファイル名の指定もDRYじゃありません。{}でグループ化しましょう。

{
  echo "なにかメッセージ"
  ls
  別のコマンド 
} >> logfile.txt

ちなみに、別記事で紹介されている&&、||の条件判断とグループ化を合わせるとこんな書き方ができます。

[ -f testfile.txt ] || {
   echo "file not found."
   exit 1
}

grep word file.txt > tmp.txt && {
   tmp.txtを使った処理
   :
   rm -f tmp.txt
}

rubyのブロックみたいでちょっとかっこいいですね。ただしset -eしていると||が分岐しないでそこで終了しちゃいます。使い捨てのものに使ってひとりでドヤるにとどめておきましょう。

シェルスクリプトを捨てる

シェルスクリプトを書く上で大事なのが、シェルを使うのをやめる判断です。シェルは難しいことをやるのに向いていません。理由はたくさんありますが*2スクリプトが大きくなるとそのうち保守が不可能になるので、技巧的だなーと思ったらさっさとシェルスクリプトを捨てて別のまともなプログラミング言語を使うことです。
代わりの言語はperlがいいでしょう。*nixならshの次に環境に入ってる確率が高くてモジュラーなプログラムが書けます*3。ちなみに、いくつかの商用UnixLinuxで仕事してきましたが、入ってる率の高いスクリプト言語を感覚的に並べるとこんな感じになります。

sh,awk > perl >>> bash > python >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ruby
エディタの件

ついでにエディタの話を。
もしあなたが画家のようなプログラマーなら、生産性の高いものを使えばよいのです。しかしもし問題解決を旨とする職人ならば、悪いことは言わないのでviを使えるようにしておきましょう。例えるならemacsは画家のアトリエみたいなもんですが、viは鉛筆1本です。他人のemacsを使わせるのが拷問として知られる一方、訓練されたvi使いはどんな環境でも仕事ができます。sh, awkと同じでどこにでもあるからです。客先のサーバにemacsが入ってることなんてまずありません。

      • -

追伸:

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

この本はとても良い本なのでおすすめしておきます。ここに書いたことは全部のってます。

*1:ちなみにこれを対話的に使うシェルでやると、コマンドtypoして強制ログアウト、未定義の変数展開で強制ログアウト、コマンドがエラーを返したら強制ログアウトという超ハードモードになります。.bashrcに書くなよ。絶対に書くなよ

*2:サブシェル問題とか動的スコープとか、シェル関数がまともな関数機構ではないとか

*3:ただしver 5.6だったりしますが