web-dev-qa-db-ja.com

パスに依存しないシバン

2台のマシンで実行できるようにしたいスクリプトがあります。これら2つのマシンは、同じgitリポジトリからスクリプトのコピーを取得します。スクリプトは適切なインタープリターで実行する必要があります(例:zsh)。

残念ながら、両方envzshは、ローカルマシンとリモートマシンの異なる場所にあります。

リモートマシン

$ 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を使用するようにシバンを設定するにはどうすればよいですか?

20

シバンは完全に静的であるため、シバンを通じて直接これを解決することはできません。この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することを防ぐことができます。

22
Andreas Wiese

何年もの間、スクリプトを実行する必要があるシステム上でBashのさまざまな場所を処理するために同様の方法を使用してきました。

Bash/Zsh/etc。

#!/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

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行の前にあるすべての行をスキップして、ファイルを解析します。

7
slm

シバンを修正する自己変更スクリプトを作成する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番目のスクリプトは前者を処理するために少し変更したものです。

1
jlliagre