シェルスクリプトを生成するシェルスクリプト

超小規模な社内サーバの管理に関わることが多いためか、管理用に「スクリプトを生成するスクリプト」を作成することがある。

スクリプトを生成する」と書くと、なにかしら大層なことをやっているように聞こえるが、何のことはない、単に可変のパラメータをテンプレートに当てはめて出力しているだけである。

4月の新卒社員の入社や、オンプレミスのサーバの入れ替えにともなうデータ移行などでは、似たような作業を何度も繰り返す場面がある。例えばアカウントやグループの追加などだ。こういった場面では、事前にアカウント名などの一覧を作成しておき、それらをテンプレートに当てはめて、「アカウントを一括追加するスクリプト」を生成しておく。

生成したスクリプトは、事前に内容をチェックしておき、後で*1スクリプトを実行してシステムに適用する。

そして使用済みのスクリプトは、システムの運用記録の一部として保存しておく。

この手法を、まだPowerShellが無かったころにWindows Serverで採用した時は、「RubyスクリプトWSHVBScript)のスクリプトを生成する」という、少々大げさな構成だった。システムをスクリプトで十全に管理するためにはWSHを使うしかなかったのだが*2スクリプト生成自体はテキスト処理が得意な別の言語*3を使わないと辛かった。

(最近のWindows Serverはまともに触れていないのだが、PowerShellを使えば「PowerShellPowerShellスクリプトを生成する」という感じになりそうだと思っている)

今はLinuxサーバを運用しているが、Unix系のファイル・ベースの世界なので、「シェルスクリプトシェルスクリプトを生成する」というシンプルな構成に落ち着いている。シェル環境はテキスト処理もそれなりにイケる口だし、システム管理自体もシェル・コマンドで済むので、こういう芸当が可能となる。

つい最近サーバを引っ越したのだが、ディレクトリ・サービスなんて存在しない環境なので、「引っ越し元のユーザとグループを、uidとgidが同じ値となるように、引っ越し先に登録する」という作業を行う必要があった。

全ユーザの情報は「getent passwd」で取得できるが、システム管理用のアカウントも含まれている。引っ越し元の環境では、一般ユーザのuidは1000以上なので、まずはuidでフィルタリングすればよい。ただし例外として、nobodyのuidは「1000以上」に該当するので、追加でフィルタリングする。

getent passwd |
awk -F : '$3 >= 1000 && $1 != "nobody"'

これで、例えば次のような出力が得られる。

foo:x:1000:1000::/home/foo:/bin/rbash
bar:x:1001:1001::/home/bar:/bin/rbash
baz:x:1002:1002::/home/baz:/bin/rbash

この出力を、例えばuseradd(8)のコマンドの羅列に変換してしまえば、後は引っ越し先で実行するだけで済む(パスワード・ログインが絡んでくると厄介だが、このサーバでは、一般ユーザはsshの公開鍵認証でのログインのみ許可している。また制限付きシェルを使用して、利用可能な機能を制限しており、su(1)やsudo(8)なんて夢のまた夢状態である。なので、この程度の変換で済む)。

getent passwd |
awk -F : '
$3 >= 1000 && $1 != "nobody" {
    print "useradd -b /home -g " $4 " -s " $7 " -u " $3 " " $1
}'

結果はこんな感じ。

useradd -b /home -g 1000 -s /bin/rbash -u 1000 foo
useradd -b /home -g 1001 -s /bin/rbash -u 1001 bar
useradd -b /home -g 1002 -s /bin/rbash -u 1002 baz

コマンドの生成はワンライナーでも可能だが、実際のサーバ運用では、引っ越し直前に急なアカウントの変更が発生することがある。なので、コマンドの生成処理自体をスクリプト化しておき、直前にアカウントの変更が発生した場合には再度スクリプトを実行するだけで済むようにしておく。

#!/bin/sh

getent passwd |
awk -F : '
BEGIN {
    print "#!/bin/sh"
}
$3 >= 1000 && $1 != "nobody" {
    print "useradd -b /home -g " $4 " -s " $7 " -u " $3 " " $1
}' >02-make-users.sh

実のところ、サーバ管理はほぼ独学でやっているので、このような手法が一般的なのかどうなのか分からない。でも、まあ、私のような素人が思いつくような方法なので、別段変わったやり方ではないと思う。

ただし、おそらく「大きな会社のシステム管理」や「大規模システムのインフラ管理」という「利用者の同質性が低い」環境では、このような手法は危険だろう。スクリプトを生成するとなると、本来はユーザ名などの可変パラメータのバリデーション(というかエスケープ処理)を真面目に考える必要があって、そこで七転八倒して苦しむぐらいなら最初から「スクリプトを生成する」ではなく「スクリプト内で処理までしてしまう」に舵を切ってしまった方が安全だ。

良くも悪くも、同質性の高い少人数が利用しているサーバだからこそ可能な手法だろう。

まあ、モダンなサーバ管理では、より安全で簡単な手法が採用されているに違いない。

*1:新卒社員の正式入社日や、サーバの入れ替え作業実施日など。

*2:システムの細かいところに手を加えようとした時、バッチファイルでは不十分だった。

*3:当時だとPerlPythonRuby