Perl Hackers Hub

第38回 Perlで作るシステム運用ツール(2)

この記事を読むのに必要な時間:およそ 4.5 分

前回の(1)こちらから。

Perlとシェルとの連携

ここからはデプロイスクリプトを例に,Perlで運用をサポートする勘所を見ていきます。

UNIX系システムの日々の運用では,コマンドを多用します。Perlは,プログラムからコマンドを呼ぶ複数の方法を持っています。今回はその中からsystem関数,``(バッククオート)⁠IPC::Open3の3つを紹介します。

system関数でコマンドを呼ぶ

systemはPerl組込みの関数で,引数に渡されたコマンドを実行します。リスト1のようなプログラムを実行すると,カレントディレクトリのファイル一覧が表示されます。

リスト1 system関数によるコマンドの実行

use strict;
use warnings;

# カレントディレクトリのファイル名一覧を表示する
system "ls";

この関数に渡されたコマンドは内部的にはforkで作られた子プロセス上で実行され,プログラムはその子プロセスの終了を待ちます。⁠子プロセス」と何の前置きもなしに言ってしまいましたが,プロセスについてよくわからないという人は,拙著process-bookがWeb上で無料公開されていますので,ぜひ読んでみてください。

system関数は,引数が1つの場合と複数の場合と間接オブジェクト記法を利用した場合で挙動が異なります。

引数が1つの場合

引数が1つの場合で,なおかつその中にシェルのメタ文字が含まれている場合,その引数はシェルに渡され,展開されますリスト2)⁠

リスト2 シェルのメタ文字を含む文字列を引数にした場合のsystemの挙動

use strict;
use warnings;

# カレントディレクトリのファイル名一覧を表示する
system "echo ./*";

シェルのメタ文字が含まれていない場合は,空白によって分割され,それらがシェルを経由せずにexecvp(3)に直接渡されます。この場合はシェルを起動するコストがないため,より効率的に実行できます。

execvp(3)とは,いわゆるexecシステムコールの本体です。プログラムの中でexecシステムコールを呼び出すと,そのプロセスはexecの引数に渡されたプログラムで置き換えられ,実行されます。プロセスが置き換えられてしまっているため,execで実行されたプログラムが終了しても,呼び出し元のプログラムには処理が戻ることはありません。

前述の通り,system関数では子プロセス上でexecが実行されます。これにより,親プロセスが置き換えられることを防ぎ,systemで実行されたプログラムが終了したあとに,呼び出し元のプログラムに処理を戻しています。

引数が2つ以上の場合

引数が2つ以上ある場合も,それらの引数は直接execvp(3)に渡され,シェルを経由しません。そのため,シェルのメタ文字もシェルによって解釈されず,直接渡されます。リスト3ならば,./*がシェルで展開されず,そのまま./*という文字列が表示されます。

リスト3 引数が2つ以上ある場合のsystemの挙動

use strict;
use warnings;

# "./*"という文字列を表示する
system "echo", "./*";

引数が1つでシェルのメタ文字を含まない場合と,引数が2つ以上ある場合は,どちらもシェルを経由せずに直接execvp(3)に渡されるわけですが,その挙動は微妙に異なります。それを確認するために,まずは渡された引数の数を表示するだけのPerlプログラムを書いて,そのプログラムをsystem関数で実行してみましょうリスト4,リスト5)⁠

リスト4 引数の数を表示するプログラム(print_argc.pl)

#!/usr/bin/env perl
use strict;
use warnings;

print scalar @ARGV, "\n";

リスト5 systemでprint_argc.plを呼ぶ

use strict;
use warnings;

my @command = ("./print_argc.pl", "a", "hello world");
my $command_string = join(" ", @command);

system @command; # => 2
system $command_string; # => 3

リスト5を見るとわかるとおり,単一の引数の場合は,引数に「空白を含む文字列」を渡したときにその空白によって引数が分解され,結果として"hello world"という文字列は"hello""world"に分解されてしまいます。一方で,2つ以上の引数を取った場合,Perlで定義したままプログラムの引数として渡されていることが確認できます。

常にシェルを経由しない安全な呼び出しを行う

シェルを経由してコマンドを呼び出す場合,*が展開されてしまって意図しない挙動になるなど,安全でない呼び出しになってしまいそうです。そこで,常にシェルを経由せずにexecvp(3)に渡される方法はないのでしょうか。実は,間接オブジェクト記法というものを使うことで,常にシェルを経由せずにコマンドを呼び出せます。

間接オブジェクト記法とは,func a b, c;のように,最初の引数のあとにカンマを書かない関数の呼び出し方法です。systemに対してリスト6のように間接オブジェクト記法を利用することで,

  • シェルを経由せず最初の引数を実行する
  • そのプログラムの引数として,3つ目以降の引数が渡される
  • プログラムの「名前」psCMDに表示されるもの)は2つ目以降の引数を合わせたものとなる

という挙動を実現できます。この場合は,systemの最初の引数に"yes"を渡しているため,yesコマンドが実行されます。yesコマンドの引数には@commandsの2つ目以降,つまり"hello world"が渡されますので,このプログラムを実行するとコンソールに延々とhello worldが出力され続けます。そして,このプロセスを別のターミナルからpsなどで確認すると,CMDのところには!!!yes!!! hello worldと表示されます。

リスト6 確実にシェルを経由せずにコマンドを実行する

use strict;
use warnings;

my @commands = ("!!!yes!!!", "hello world");

system {"yes"} @commands;
コマンドの終了コードを取得する

system関数は,内部的にはforkした子プロセスでコマンドを実行するというのは前述のとおりですが,戻り値としてその終了コードを返します。正常に終了したコマンドは0を終了コードとして返すので,戻り値を見ることで,コマンドの成功/失敗を確認できるわけです。

注意点として,実際の終了コードを得るためには返された値を右に8ビットシフトする必要がありますが,0を右にビットシフトしても0ですので,コマンドが正常に成功したかどうかを見るだけならば,0であるかどうかを確認するだけで問題ありませんリスト7)⁠

リスト7 system関数の戻り値で終了コードを取得する

use strict;
use warnings;

my $failed = system "false";
print $failed . "\n"; # => 256

my $succeeded = system "true";
print $succeeded . "\n"; # => 0
system関数を利用してデプロイスクリプトを作る

さて,今回はデプロイスクリプトを例にとると言いましたが,紙幅の都合上,静的ファイルを複数のサーバに配るだけのデプロイスクリプトを考えます。rsyncで配ってもよいのですが,複数のサーバに配るという性格上,

  • どこかのサーバへのデプロイに失敗したらロールバックする
  • 複数のサーバのファイルをなるべく同じタイミングで更新する

という2点を重視し,静的ファイルをtarでアーカイブしたものを複数のサーバに配り,タイムスタンプから作ったディレクトリに対して中身を配置し,すべてのサーバに無事に配置できたらシンボリックリンクをそのディレクトリに対して張る,という戦略でいきます。

まずは特定のディレクトリをtarでアーカイブするスクリプトを書いてみましょうリスト8)⁠

リスト8  staticというディレクトリをアーカイブするスクリプト

use strict;
use warnings;
use File::Basename qw/dirname/;

my $static_file_dir = dirname(__FILE__) . "/static";
my $tar_command =
  ["tar", "zcvf", "static.tgz", $static_file_dir];

my $ret = system @$tar_command;
if ( $ret != 0 ) {
  die "archive failed";
}

次に,アーカイブしたファイルをサーバへ配る部分を書いていきたいところですが,Perlにはsystem関数以外にもコマンドを呼ぶ方法が存在します。そこで,それぞれの方法の違いを確認するためにも,ほかの方法について先に見ておきましょう。

著者プロフィール

丸山晋平(まるやましんぺい)

1984年生まれの新潟県出身文系プログラマ。ScalaとPerlと猫が好き。

TRIO the CMYKというバンドでベースを担当。バンドは育休で限定稼働中。

尊敬するひとは結城浩先生。

コメント

コメントの記入