rcmdnk's blog

UNIXシェルスクリプトコマンドブック 第3版 電子書籍: 山下 哲典

シェルスクリプトを書く時にShebangに#!/bin/shの様にシェルの指定をしますが Bashの機能を使いたい場合はきちんとbashを指定しないといけません。

その辺の違いは早いうちに気づくことがあると思いますが、 cronの中でのシェルについてちょっと見落としていたのでその辺について。

Sponsored Links

/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という変数を指定しておくとそのシェルで実行してくれます。

Sponsored Links
Sponsored Links

« Facebookのシェア数の獲得方法のアップデート macOS Sierraにアップグレードしてみて取り敢えずの状況 »