perlbrewを使うにあたっていろいろな小細工をした件

最近perlbrewを使っています。で、いろいろ小細工をしたので問題点とその解決方法のまとめです。

補足すると、深遠な理由とかマリファナ海峡より深い事情とかがなければ、バッチ用、shebang用に(↓で書いてる)appperl的なラッパーを用意するだけでいけるんじゃないかと思います。

問題点

  1. perlbrewのサイトを見るとビールがのみたくなる
  2. perlbrewedなperlの実行方法
  3. サーバーワイドな共通の@INC


perlbrewedなperlの実行方法

perlbrewedなperlをどう実行するか、3つの局面にわけて考えます。

対話シェル上で

基本、~/.bashrcで $PERLBREW_ROOT/etc/bashrcを読めばOK。

なのですが、(深淵なる理由があり)同じUNIXユーザー(oreno)でプロジェクトごと(curryとgyoza)にperlbrewを使い分けたい

/home/oreno/curry/perlbrew/
/home/oreno/gyoza/perlbrew/

ので、こんなふうにしています。

# source this file in your ~/.bashrc
PROJECT_HOME=

if [ ! -z "$PROJECT" ]; then
  PROJECT_HOME="$HOME/$PROJECT"
else
  PROJECT_HOME="$HOME"
fi

### prepare perlbrew
if [ -d "$PROJECT_HOME/perlbrew" ]; then
  PERLBREW_ROOT="$PROJECT_HOME/perlbrew"
  export PERLBREW_ROOT
  PERLBREW_HOME="$PROJECT_HOME/perlbrew"
  export PERLBREW_HOME

  [ -f "$PERLBREW_ROOT/etc/bashrc" ] && . "$PERLBREW_ROOT/etc/bashrc"
  [ -f "$PERLBREW_ROOT/etc/perlbrew-completion.bash" ] && . "$PERLBREW_ROOT/etc/perlbrew-completion.bash"
fi

環境変数PROJECTはどうやってログイン時にセットされているかというと、~oreno/.ssh/authorized_keysで指定しています。

environment="PROJECT=curry" ssh-rsa ...
environment="PROJECT=gyoza" ssh-rsa ...

curryとgyozaと担当者が同じ場合は、鍵を2つ作ってssh curryとssh gyozaとで鍵を使い分けるようないい感じの設定をSSHアクセス元の ~/.ssh/config に書けばいいと思います。

なお、environment= を使うには、sshd_config に PermitUserEnvironment yes が必要です。

ちなみに、sshで別ホスト(サーバーファーム内のwebサーバー達とか)にログインした際にもこの環境変数を引き継ぐようにするには、

sshd_configに、

AcceptEnv PROJECT

ssh_configに、

SendEnv PROJECT

と書けば実現できます。


PERLBREW_HOMEをPERLBREW_ROOTと同じに設定しているのは、空間を完全に分けたいからです。(PERLBREW_HOMEのデフォルトは~/.perlbrew)


これで、対話シェルでは「perl」と打てばperlbrewedなperlが実行されるようになります。

スクリプトshebang (#!)

対話シェル上でperlスクリプトを実行するときは、

$ perl ./mouyan.pl

とすればperlbrewedなperlで実行できるのですが、いちいち「perl 」と前置するのはめんどいです。

対話シェルでは、前掲の設定がなされていれば「perl」で実行されるのはperlbrewed perlなので、shebangを「#!/usr/bin/env perl」にすればOKです。


さて、shebangが「#!/usr/bin/perl」と書かれていて、かつ、書き換えられない|書き換えたくない深淵な理由がある場合はどうするかというと、/usr/bin/perlをラッパースクリプに置き換えちゃいます。

#!/bin/dash

PERL=$(which perl 2>/dev/null)
SYSTEM_PERL="/usr/bin/perl5.10.1"

if [ -z "$PERL" ]; then
  exec $SYSTEM_PERL "$@"
elif [ "$PERL" = "/usr/bin/perl" ]; then
  exec $SYSTEM_PERL "$@"
else
  # maybe perlbrewed perl
  exec perl "$@"
fi

echo "failed to exec perl: $PERL" 1>&2
exit 2

perl」でどのパスのperlが実行されるか調べて、perlbrewedっぽくなかったらsystem perlを実行するようにしています。


ここで注目して欲しいのでこのラッパースクリプトshebangです。/bin/bashじゃなくて/bin/dashにしています。(dashはbashとは別のshの実装です)

bashは、環境変数 BASH_ENV が設定されていると非対話シェル(シェルスクリプトとか)でも BASH_ENV で指定したファイルが読まれます。

つまり、BASH_ENV=~/.bashrc な状態で、#!/bin/bashな/usr/bin/perl を実行すると、

  1. /usr/bin/perlbashスクリプトなので、まず (BASH_ENVで指定された) ~/.bashrc が読まれる
  2. もし ~/.bashrc の中で perl を実行していると /usr/bin/perl が実行されて、
  3. /usr/bin/perlbashスクリプトなので、まず (BASH_ENVで指定された) ~/.bashrc が読まれる
  4. ~/.bashrc の中で perl を実行しているので /usr/bin/perl が実行されて、
  5. /usr/bin/perlbashスクリプトなので、まず (BASH_ENVで指定された) ~/.bashrc が読まれる
  6. ~/.bashrc の中で perl を実行しているので /usr/bin/perl が実行されて、
  7. \(^o^)/

となります。

なので、#!/bin/dash と書いています。

#!/bin/shでもOKです。/bin/shbashのsymlinkでも、「sh」という名前で実行された場合はbashはその振る舞いを変え、BASH_ENVが評価されないからです。

cronで実行する場合

対話シェル上での話はこれで片付いたので、次は非対話な環境での実行時です。

対話シェルのときのようにはperlbrewの環境設定することができないので、~oreno/curry/bin/appperl や ~oreno/gyoza/bin/appperl にラッパースクリプトを使って、それ経由で実行するようにします。

#!/bin/bash
#
# ~oreno/curry/bin/appperl
#

export PERLBREW_ROOT=/home/oreno/curry/perlbrew
export PERLBREW_HOME=/home/oreno/curry/perlbrew

. $PERLBREW_ROOT/etc/bashrc

if [ -z "$PERLBREW_PERL" ]; then
  perl_version=perl-5.16.1
else
  perl_version=$PERLBREW_PERL
fi

perlbrew use $perl_version
exec perl "$@"

echo "failed to exec: $@" 1>&2

最近のperlbrewですと、execでバージョン指定ができる

$ perlbrew exec --with 5.16.1 perl -v

のですが、

perl-5.16.1
==========

というのが出力されるのを嫌って perlbrew use してから perl を実行するようにしています。

perlじゃなくてappperlなのは、このラッパースクリプト内で「perl」を実行しているので、PATH的に~oreno/curry/binが先にあると再度自分を実行して…とループしてしまうからです。(system perlを実行するようにすれば回避はできますが)


で、orenoユーザーのcrontabには、こんな感じに書きます。

### curry
* * * * *  ~/curry/bin/appperl  ~/curry/batch/oomori.pl

### gyoza
0 8 * * *  ~/gyoza/bin/appperl  ~/gyoza/batch/3ninmae.pl
0 0 * * *  ~/gyoza/analyze/gyoza-count.pl # ←これのshebangは「#!/home/oreno/gyoza/bin/appperl」

追記 2013-02-15

appperlを実行した場合、perlの起動が遅いです。なぜなら、

となるからです。

なので appperl はラッパースクリプトじゃなくて、perlbrewed perlへのsymlinkでいいんじゃないかと思っている今日この頃です。

もろもろの起動時間。

■ 1. bash
$ time bash -c ':'

real    0m0.028s
user    0m0.005s
sys     0m0.020s

■ 2. dash (bashとの比較参考用)
$ time dash -c ':'

real    0m0.004s
user    0m0.001s
sys     0m0.003s

■ 3. 厚いperlラッパー(appperl) (1+4+6 = 1+(1+5)+6)
$ time appperl -e 1

real    0m0.465s
user    0m0.319s
sys     0m0.110s

■ 4. システムperlのラッパー (1+5)
$ time /usr/bin/perl -e 1

real    0m0.011s
user    0m0.002s
sys     0m0.007s

■ 5. システムperlの実体
$ time /usr/bin/perl5.10.1 -e 1

real    0m0.007s
user    0m0.002s
sys     0m0.005s

■ 6. perlbrewed perlの実体
$ time /usr/oreno/perlbrew/perls/perl-5.16.1/bin/perl -e 1

real    0m0.008s
user    0m0.000s
sys     0m0.006s