2台のマシンで実行できるようにしたいスクリプトがあります。これら2つのマシンは、同じgitリポジトリからスクリプトのコピーを取得します。スクリプトは適切なインタープリターで実行する必要があります(例:zsh
)。
残念ながら、両方env
とzsh
は、ローカルマシンとリモートマシンの異なる場所にあります。
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
スクリプトを/path/to/script.sh
として実行すると常にZsh
で利用可能なPATH
を使用するようにシバンを設定するにはどうすればよいですか?
シバンは完全に静的であるため、シバンを通じて直接これを解決することはできません。このLCMがzshでない場合は、Shebangに(シェルの観点から)いくつかの「最も一般的な乗数」を設定し、適切なシェルでスクリプトを再実行することができます。つまり、すべてのシステムで検出されたシェルによってスクリプトが実行されるようにし、zsh
- only機能をテストします。テストがfalseであることが判明した場合は、exec
with zsh
、テストが成功し、続行するだけです。
たとえば、zsh
のユニークな機能の1つは、$ZSH_VERSION
変数の存在です。
#!/bin/sh -
[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}
# zsh-specific stuff following here
echo "$ZSH_VERSION"
この単純なケースでは、スクリプトは最初に/bin/sh
によって実行されます(80年代以降のすべてのUnixライクなシステムは#!
を理解し、BourneまたはPOSIXの/bin/sh
を持っていますが、構文は両方)。 $ZSH_VERSION
が設定されていない場合、exec
を介してzsh
自体がスクリプトされます。 $ZSH_VERSION
が設定されている場合(それぞれ、スクリプトはすでにzsh
を介して実行されています)、テストは単にスキップされます。ボイラ。
これは、zsh
が$PATH
にまったくない場合にのみ失敗します。
編集:確認するために、通常の場所ではexec
a zsh
のみ、次のようなものを使用できます
for sh in /bin/zsh \
/usr/bin/zsh \
/usr/local/bin/zsh; do
[ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done
これにより、予期しないexec
ではない何かを$PATH
に誤ってzsh
することを防ぐことができます。
何年もの間、スクリプトを実行する必要があるシステム上でBashのさまざまな場所を処理するために同様の方法を使用してきました。
#!/bin/sh
# Determines which OS and then reruns this script with approp. Shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/Sun5/bash";
OS_TYPE=`uname -s`;
if [ $OS_TYPE = "SunOS" ]; then
$SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
Elif [ $OS_TYPE = "Linux" ]; then
$LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
echo "UNKNOWN OS_TYPE, $OS_TYPE";
exit 1;
fi
exit 0;
### BEGIN
...script goes here...
上記は、さまざまな通訳者に簡単に適用できます。重要な点は、このスクリプトが最初はBourne Shellとして実行されることです。その後、再帰的に自分自身を呼び出しますが、sed
を使用してコメント### BEGIN
の上にあるすべてのものを解析します。
Perlの同様のトリックは次のとおりです。
#!/bin/sh
LIN_Perl="/usr/bin/Perl";
SOL_Perl="/packages/Perl/bin/Perl";
OS_TYPE=`uname -s`;
if [ $OS_TYPE = "SunOS" ]; then
eval 'exec $SOL_Perl -x -S $0 ${1+"$@"}';
Elif [ $OS_TYPE = "Linux" ]; then
eval 'exec $LIN_Perl -x -S $0 ${1+"$@"}';
else
echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
exit 0;
fi
exit 0;
#!Perl
...Perl script goes here...
このメソッドは、実行するファイルを指定すると、Perlの機能を利用して、#! Perl
行の前にあるすべての行をスキップして、ファイルを解析します。
シバンを修正する自己変更スクリプトを作成する1つの方法を次に示します。このコードは、実際のスクリプトの前に追加する必要があります。
#!/bin/sh
# unpatched
PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
[ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
cp -- "$0" "$0.org" || exit 1
mv -- "$0" "$0.old" || exit 1
(
echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`"
sed -n '/^##/,$p' $0.old
) > $0 || exit
chmod +x $0
rm $0.old
sync
exit
fi
## Original script starts here
いくつかのコメント:
スクリプトが配置されているディレクトリでファイルを作成および削除する権限を持つユーザーが一度実行する必要があります。
一般的な信念にもかかわらず、/bin/sh
はPOSIXシェルであることは保証されていません。
「偽の」zshの選択を回避するために、PATHをPOSIX準拠のものに設定し、その後に可能なzshロケーションのリストを続けます。
何らかの理由で自己変更スクリプトが歓迎されない場合、1つではなく2つのスクリプトを配布するのは簡単です。最初のスクリプトはパッチを適用するスクリプトで、2番目のスクリプトは前者を処理するために少し変更したものです。