シェルスクリプトはエレガントでなければならない:スマートな紳士のためのシェルスクリプト(1)(2/2 ページ)
UNIX系OSのユーザーなら、何らかの形で「シェルスクリプト」を使ったことがあるはずだ。人からもらったスクリプトを意味も分からずに使っているとか、コマンドを並べただけのスクリプトを使っているという人は多いだろう。この連載では、そのようなシェルスクリプト初心者を脱出して、すべてを知り尽くしたシェルスクリプトマスターとなるための手助けをしたい。(編集部)
誰かが使っているスクリプトを読んでみろ
文法を知るところから始めたシェルスクリプト使いは、なかなか次の一線を越えられない。それはシェルスクリプトに限らず、ほかのプログラミング言語や自然言語でも同じことなのだが、勉強を通じて身に付けたコードというものは、現場で実際に使用するコードとは大きく異なるというところに原因がある。シェルスクリプトの文法を一通り覚えたとしても、実際に使う機能とあまり使わない機能がある。あまり使うことがない機能を必死に覚えても意味がないだろう。
また、文法を覚えただけでは使い方を想像できないが、実際にはよく使う記述方法もある。ある程度シェルスクリプトの文法を学んだら、次は実際に誰かが活用しているシェルスクリプトを読むことが、ステップアップのポイントだ。本連載ではしばらくの間はシステムで実際に使われているシェルスクリプトを読みながら、その意味を解説していこうと思う。繰り返し登場する書き方やテクニックは、その過程で何度も紹介することになるだろう。いずれ覚えてしまうはずだ。
シェルスクリプトは記述と保存さえできれば、どんな方法で記述してもよい。とはいえ、@ITでは同時進行でVimの連載が続いている。この連載でもVimを使うことにする。Vim連載の第8回の記事を参考にしてもらえれば、Vimで編集しつつショートカットキーでVimからスクリプトを実行するといったことができる。例えば、次のようなシェルスクリプトを作成してみよう。
#!/bin/sh printf "hello world!\n"
さらに、次のようなMakefileを作成してVimから実行してみてほしい。無事、“hello world!”と表示されれば成功だ。
run: ./hello.sh
Vimの連載では、今後しばらくプログラムを編集するための機能やプラグインを紹介していこうと思うので、そちらも併せてご覧いただければと思う。
シェルスクリプトを通してUNIXを学べ
シェルスクリプトでよく使う便利な機能、例えばパイプや、リダイレクト、サブシェルなどはUNIXの機能に深く関連している。例えばインタラクティブシェルでもよく使われる「2>&1」という記述は、dup2(2)システムコールでファイルディスクリプタを上書きする処理をするのと同じだ。例えば、次のようなシェルスクリプトを用意してみてほしい。
#!/bin/sh : 2>&1
このコマンドをtruss(1)でトレースすると、dup2(2)がコールされていることを確認できる。なおtruss(1)はPOSIX.1に定められたコマンドではない。プラットフォーム依存のコマンドだ。ここではFreeBSDで実行している。
% truss ./true.sh |& tail -10 stat(".",{ mode=drwxr-xr-x ,inode=3,size=121,blksize=16384 }) = 0 (0x0) stat("/home/daichi",{ mode=drwxr-xr-x ,inode=3,size=121,blksize=16384 }) = 0 (0x0) read(10,"#!/bin/sh\n\n: 2>&1\n",1023) = 18 (0x12) fcntl(2,F_DUPFD,0xa) = 11 (0xb) fcntl(11,F_SETFD,FD_CLOEXEC) = 0 (0x0) dup2(0x1,0x2,0x7fffffffd592,0x622ac0,0x0,0x624020) = 2 (0x2) dup2(0xb,0x2,0x7fffffffdbe8,0x622ac0,0x0,0x624020) = 2 (0x2) close(11) = 0 (0x0) read(10,0x6238a0,1023) = 0 (0x0) process exit, rval = 0 %
ashであれば、この部分の処理は以下に挙げた部分になる。sh/redir.cのpopredir(void)関数内のdup2()が呼ばれている部分だ。ashはコードも小さくまとまっているので調べやすい。シェルスクリプトを使いこなすには、ソースコードを読むことも必要だ。
void popredir(void) { struct redirtab *rp = redirlist; int i; for (i = 0 ; i < 10 ; i++) { if (rp->renamed[i] != EMPTY) { if (i == 0) fd0_redirected--; if (rp->renamed[i] >= 0) { dup2(rp->renamed[i], i); close(rp->renamed[i]); } else { close(i); } } } INTOFF; redirlist = rp->next; ckfree(rp); INTON; }
シェルスクリプトで利用できる機能は、UNIXが提供している基本的な機能とダイレクトに対応していることが多い。UNIXについて詳しくなればなるほど、エレガントでスマートなシェルスクリプトが書けるようになるのはこのためだ。カーネルがどう動くのか想像できるようになると、無駄のないシェルスクリプトを書けるようになる。逆説的だが、シェルスクリプトに詳しくなると、UNIXの内部構造に詳しくなっていく。
つい先日、システムコールの連載が始まった。システムコールについて詳しく知りたいというときはこちらの連載もご覧いただければと思う。
プログラミングとは別物と考えよ
シェルスクリプトの文法に慣れてきたユーザーが陥りがちなパターンの1つに、「きちんとコードを作ってしまう」問題がある。コードの重複を嫌い、変数や繰り返し構文、場合によっては2重、3重のループ処理と分岐処理を入れてコードの重複を排除する。
そのように書くことに効果があるプログラミング言語ならもちろんそうすべきだ。しかし、本連載のテーマはシェルスクリプトだ。シェルスクリプトで、プログラミング言語を使っているときのようなコードを作ろうとすることは、未来の自分に苦痛を強いることにつながることが多いように思う。シェルスクリプトではプログラミングのようなこともできるが、毎回そうすればよいというものではない。エレガントなシェルスクリプトを書くために必要なのは、的確な問題の切り分けと、スクリプトを必要以上に難しくしないという意識だ。
UNIXの仕組みが分かってくると、この手のプログラミング言語を使っているときのようなコードを記述せずに、UNIXの内部構造を意識したシンプルでエレガントなシェルスクリプトを記述できるようになる。プログラミングらしいプログラミングが必要な場合、それはシェルスクリプトではなくC/C++などの汎用プログラミング言語を使ってコマンドとして実装した方が良いパターンということもある。
シェルスクリプトで必要となるテクニックのいくらかを、筆者は「アットアグランス性」とか「コピーアビリティ」というちょっと怪しい言葉を当てて呼んでいる。このような造語もいずれ解説していく。
Copyright © ITmedia, Inc. All Rights Reserved.