前回に引き続き、今回もOS付属のシェルスクリプトを読んでいく。「本当にこれでいいのか?」と思うような読みにくい記述も見つかるが、よく読むとシェルスクリプトならではの流儀を学ぶことができる(編集部)
他人の書いたシェルスクリプトを読むことで、これまでまったく知らなかった書き方を知ることができたり、なぜそうした書き方をしているのかに思案を巡らすことができる。シェルスクリプトはガッチリしたプログラミング言語とは性格が異なっている。現場での実際の書き方を知ることが、スキルアップにおいて大切な要素となる。
これまでに引き続き、今回もシェルスクリプトで開発されたFreeBSDのコマンドを読み解いていく。順番に従い、今回は/sbin/resolvconfから参考になる書き方を取り上げて紹介する。
resolvconf(8)は/etc/resolv.confを管理するデーモン。システムには通常1つの/etc/resolv.confファイルがあり、サービスはこのファイルの内容に従って名前解決を試みる。所属するネットワークが1つの場合にはこれで問題ないが、無線LANと有線LANに同時に接続していたり、VPNを利用している場合など、複数のネットワークに接続している場合には、単一の/etc/resolv.confファイルをサービスが個別に上書きしていては問題が発生する。
resolvconf(8)はこの問題を解決するためのプログラム。/etc/resolv.confを書き換えたいと考えるサービスは、直接ファイルを書き換える代わりに、resolvconf(8)へファイルを送信する。resolvconf(8)は送られてくるデータを整理して、ベストだと考える/etc/resolv.confファイルを生成する。
通常、ユーザーがresolvconf(8)を直接利用することはあまりないと思う。背後ではこうしたプログラムが活用されているということだけ把握しておけばよいだろう。
ほかのシェルスクリプトを読み込んで実行する処理は「.」で実施できる。「.」の後に指定したファイルが、その場に読み込まれ実行される。resolvconf(8)では次のように、設定ファイルを読み込むために「.」が使われている。
. "$SYSCONFDIR"/resolvconf.conf
「.」が使われるケースは主に2つある。1つは上記のように設定を読み込むため、もう1つは関数などが定義されたファイルを読み込むといったライブラリ的な使い方をするためだ。設定ファイルの読み込みとして使われる場合には、読み込み対象のファイルには変数の定義のみが書き込まれていることが多い。
/etc/以下にインストールされるシェルスクリプトでは「.」を使ってライブラリ的なシェルスクリプトが使われているが、個別のコマンドや自分で作成するコマンドでこの処理を活用する場合には、注意が必要になる。
まず、「.」を多用すると後でシェルスクリプトを読み返した時に読みにくくなる。「.」で呼び出した先でさらに「.」を使うなど、混乱を招くケースもある。また、「.」で呼び出すファイルに想定しない処理が記述されていても実行してしまうため、安全性という面でも懸念が残る。
「.」やevalもそうだが、この手の機能は便利な半面、危ない面も含んでいることを把握しておきたい。
次の記述は、変数のデフォルト値を設定する場合によく用いられる。「:」は何もしないコマンドだ。
: ${interface_order:=lo lo[0-9]*}
FreeBSD sh(ash)の場合、/usr/src/bin/sh/eval.cの1252行目あたりにあるtruecmdという関数がこのコマンドの実態。次のように0で終了するだけの処理をする。つまり上記の記述をした場合、コマンド自体は何もせず、「${interface_order:=lo lo[0-9]*}」が評価されるという処理になる。
truecmd(int argc __unused, char **argv __unused) { return 0; }
変数における「:=」は、その変数が設定されていないかまたはヌルの場合に、「:=」の右側の文字列を変数に代入するという処理になる。つまり、この書き方で変数が設定されていない(この場合には、設定ファイルで設定されていない、といった処理を想定していることになる)場合に、デフォルト値を設定する処理として機能していることになる。
手続き型のプログラミング言語の視点から見ると不思議な書き方に見えるが、これはシェルスクリプトではよく使われる書き方なので覚えてしまいたいところだ。
「>&2」は「1>&2」と同じ。「>&」は左側のディスクリプタをクローズし、右側のディスクリプタの複製を利用する処理になる。従って「>&2」で標準出力を標準エラー出力へ差し替えるといった処理になる。実際にはdup2(2)システムコールでこの処理が実施されている。
error_exit() { echo "$*" >&2 exit 1 }
上記の書き方で、echoの出力を標準エラー出力に向けるといった処理になる。エラー出力をする場合によく使われる書き方だ。
ちょっと長い引用になるが、有益なテクニックなのですべて引用しておきたい。
usage() { cat <<-EOF Usage: ${RESOLVCONF##*/} [options] Inform the system about any DNS updates. Options: -a \$INTERFACE Add DNS information to the specified interface (DNS supplied via stdin in resolv.conf format) -m metric Give the added DNS information a metric -p Mark the interface as private -d \$INTERFACE Delete DNS information from the specified interface -f Ignore non existant interfaces -I Init the state dir -u Run updates from our current DNS information -l [\$PATTERN] Show DNS information, optionally from interfaces that match the specified pattern -i [\$PATTERN] Show interfaces that have supplied DNS information optionally from interfaces that match the specified pattern -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to the console -h Show this help cruft EOF [ -z "$1" ] && exit 0 echo error_exit "$*" }
これはヒアドキュメントと呼ばれる書き方で、「<<」の右側に指定した文字列が現れるまで、記述された文字列がそのままコマンドの標準入力に渡されるというものだ。ヒアドキュメントの文字列は、変数置換、コマンド置換、算術置換、バックスラッシュクォートは評価されるが、チルダ展開やファイル名グラブ、ダブルクォートやシングルクォートは機能しない。「<<」の右側に指定する文字列を「"」または「'」でクォートした場合には変数置換、コマンド置換、算術置換、バックスラッシュクォートも機能せず、そのまま文字列として表示される。
ヒアドキュメントは、シェルスクリプトの記述においてインデントを壊すことになるため、これを嫌って使わない開発者も多い。その場合には行ごとにechoが使われることが多い。しかし、インデントをタブで統一している場合には、ヒアドキュメントでインデントを利用できる。
タブを使うには、「<<」ではなく「<<-」を使う。ヒアドキュメントの指定に「<<-」を使うと、行の先頭に連続するタブは無視されるようになる。
上記usage()関数は、コードがタブでインデントされており、実際に出力される段階ではタブは出力されない。「<<-」を使うことでシェルスクリプトのインデントを保ちつつ、出力内容も適切なものとすることができる。すべての行をechoで記述するよりも、ヒアドキュメントの方が編集しやすく作業しやすい。ぜひ覚えておきたい。
Copyright © ITmedia, Inc. All Rights Reserved.