シェルスクリプトを書く時にShebangに#!/bin/sh
の様にシェルの指定をしますが
Bashの機能を使いたい場合はきちんとbash
を指定しないといけません。
その辺の違いは早いうちに気づくことがあると思いますが、 cronの中でのシェルについてちょっと見落としていたのでその辺について。
/bin/shと/bin/bash
RedHat系やMacなどでの/bin/sh
RedHat系のLinuxで/bin/shを見てみると、
$ ls -l /bin/sh
0 lrwxrwxrwx 1 root root 4 Sep 27 2014 /bin/sh -> bash
みたいにbash
へのシンボリックリンクになっています。
Macだと
$ ls -l /bin/sh
-r-xr-xr-x 1 root wheel 632672 Dec 3 2015 /bin/sh
特にリンクにはなっておらずそのまま実体があります。 どういうものか見てみると
$ /bin/sh --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)
Copyright (C) 2007 Free Software Foundation, Inc.
な感じで実はBashで/bin/bashにあるものと同じ内容を返してきます。
全く同じ物なのかと思いきや、実際立ち上げてみると
bash
感覚で作業しようとすると出来ないことがいくつか出て来ると思います。
オプションを見てみると
posix
と言うオプションがbash
ではoff
で、
sh
だとon
になっています。
sh $ set -o
...
posix on
...
従って違うものになっていて、shの方はPOSIX mode 1 になっています。 POSIXモードではPOSIX標準の動作になり Bashによる拡張機能が制限されます。
RedHat系の場合ではシンボリックリンクにも関わらず
違う状態になります。
これはbash
のプログラムが第0引数のプログラム名を見てshだった場合には
posix
をオンにする、というオプションを持っているからです。
実際、適当なところで
$ ln -s /bin/bash sh
$ sh # launch sh
$ set -o
...
posix on
...
と自分で作ったシンボリックリンクでも/bin/shと同じ様になります。
これはvimdiff
とかでも同じです。(vim
のシンボリックリンクですがvim -d
、と-d
のオプションが自動的に加わる
2。)
この状態はbash --posix
というオプションを使っても入れます。
POSIX モードだと
$ cat <(ls)
sh: syntax error near unexpected token `('
みたいに<()
でコマンド結果をファイル入力として渡す方法(プロセスリダイレクト)が使えません。
などなど、通常のBashだと思って使おうとすると上手く行かないときもあるので きちんとどちらを使うか意識しないといけません。
UbuntuなどDebian系の/bin/sh
Ubuntuも含むDebian系では/bin/shは
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Mar 1 2012 /bin/sh -> dash
みたいな感じでbash
ではなくdash
にリンクされています。
ので、そもそもBashとは違うものなので気をつけないといけません。
dash
もPOSIX準拠なシェルですが、bash
に比べてかなり機能が制限されていて、
bash
のPOSIXモードよりも機能が少ないシェルです。
シェルスクリプトに書くShebang
ということで、シェルスクリプトでbash
を使いたい際にはきちんとbash
を指定するべきですが、
システムにいくつかbash
がインストールされている場合などに
/bin/bash以外の物を使いたい場合もあります。
その場合、/usr/bin/bashの様に書いても良いわけですが、 それだと/usr/bin/bashが無いシステムでエラーが起きます。
こういった場合を考えて、
#!/usr/bin/env bash
の様にenv
を使ってbash
を呼ぶと、現在使ってるPATH
の中で
最初に出てくるbash
を使ってくれます。
env
自体は大概のシステムで/usr/bin下にあるはずです。
RedHat系だと/bin/envに実体がありますが、 /usr/bin/envにシンボリックリンクが貼ってあります。
MacやDebian系のLinuxだと/bin/envは無くて/usrbin/envにあります。
もちろん、システムによってインストールされているbash
が違うので、
出来ることなら
#!/bin/sh
で、dash
であっても動く様なものが作れれば移植性も良くなりますが、
ちょっと複雑なことをしようと思うと新しいbash
とかを使いたくなることもあるので
その辺は適時。
cronジョブの時に使われるシェル
今回ちょっとはまったところですが、 cronジョブで何かジョブを流すとき、 もちろんシェルスクリプトを流すのであればその中のShebangによってシェルが決定されるわけですが、 そうではなくて
0 0 * * * $HOME/mycrornjob.sh > log
みたいに、cronジョブの設定の中でログの書き出しとかをしたりするとします。
この設定はどこでも上手く行きますが、
0 0 * * * $HOME/mycrornjob.sh >& log
みたいにすると、
sh: 1: Syntax error: Bad fd number
みたいなエラーがシステムメールに送られてきます。 (出力がどこにも書き出されてない時に送られるもの。)
これ出た時にしばらくスクリプト自体を色々眺めてましたが、問題はスクリプトではなくて ここの書き方です。
問題のBad fd number
と言うのは>&
が使えないシェルで使ってる時に出るエラーです。
このcronジョブを実行する時に使われるシェルが、 通常/bin/shになってるため、そこで使えないとエラーが出ます。
でこれが起こってるのは Debian系のLinuxだけ。
つまり、BashのPOSIXモードでは>&
の出力(標準エラー出力も標準出力と同時に同じところに書き出す)
が出来ますが、
dash
だと出来ません。
というわけで、どこでも共通の書き方をするには
0 0 * * * $HOME/mycrornjob.sh > log 2>&1
の様に書く必要があります。
ちなみに、cronジョブを実行するシェルを変更したい場合は、 cronジョブの記述の最初の方に
SHELL=/bin/bash
0 0 * * * $HOME/mycrornjob.sh >&1 log
の様にSHELL
という変数を指定しておくとそのシェルで実行してくれます。