web-dev-qa-db-ja.com

Bashスクリプトが完全なパスを取得するための信頼できる方法

フルパスを知る必要があるBashスクリプトがあります。私は、相対パスやファンキーなパスを使わずに、互換性のある方法を見つけようとしています。私はsh、cshなどではなく、Bashのみをサポートする必要があります。

私がこれまでに見つけたもの:

  1. 内からBashスクリプトのソースディレクトリを取得するための一般的な回答は、dirname $0を使用してスクリプトのパスを取得することです。これは、相対パス(.など)を返す可能性があります。これは、スクリプト内のディレクトリを変更し、そのパスが引き続きスクリプトのディレクトリを指すようにする場合に問題になります。それでも、dirnameはパズルの一部になります。

  2. OS XでのBashスクリプトの絶対パスに対する受け入れられた回答(OS X固有、しかし回答は関係なく動作します)は、$0が相対的に見えるかどうかをテストし、もしそうであれば、$PWDをその前に追加するかどうかをテストする関数を提供します。しかし、結果にはまだ相対ビットが含まれている可能性があります(全体としては絶対的ですが)。たとえば、スクリプトがディレクトリ/usr/bin内のtで、/usrに入っていて実行にbin/../bin/tと入力した場合(はい、複雑です)、スクリプトのディレクトリパスとして/usr/bin/../binで終わります。どれが動作します、しかし...

  3. このページのreadlinkによる解決方法 、これは次のようになります:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    しかしreadlinkはPOSIXではなく、どうやらこの解決法はGNUのreadlinkに依存しています。ここでBSDは何らかの理由で機能しません(私はBSD風のシステムにアクセスしてチェックしていません)。

それで、それをするさまざまな方法が、彼らはすべて彼らの警告を持っています。

もっと良い方法は何ですか? 「より良い」とは、

  • 絶対パスを教えてください。
  • 複雑な方法で呼び出された場合でもファンキーなビットを取り出します(上記の#2のコメントを参照)。 (例えば、少なくとも適度にパスを正規化する)
  • Bashイズムや、最も人気の高い* nixシステム(GNU/Linux、BSD、BSD風のOS Xなどのシステム)に確実に依存するものにのみ依存しています。
  • 可能であれば外部プログラムを呼び出すのを避けます(例えば、Bash組み込みを好む)。
  • 更新された、頭を上げてくれてありがとう、 wich )それはシンボリックリンクを解決する必要はありません(実際、私は親切です)それが彼らを一人にしたほうが好ましいのですが、それは必要条件ではありません)。
605
T.J. Crowder

これが私が思いついたものです(編集:+ sfstewmanlevigrokerKyle Strand 、および Rob Kennedy )。 「より良い」基準:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

そのSCRIPTPATH行は特に回り道のようですが、スペースやシンボリックリンクを適切に処理するためにはSCRIPTPATH=`pwd`ではなく必要です。

また、アクセス可能なファイルシステム内のファイルから来ていないスクリプトを実行するなどの難解な状況(これは完全に可能です)は、そこには対処されていません(または私が見たその他の答えのいずれでも)。 ).

470
T.J. Crowder

realpathコマンドがここで言及されていないことに驚きました。私の理解するところでは、それは広く移植可能/移植されているということです。

あなたの最初の解決策は以下のようになります。

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`

シンボリックリンクを好みに合わせて未解決のままにするには、

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
182

Bashで完全な正規パスを取得するために見つけた最も簡単な方法は、cdpwdを使用することです。

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

${BASH_SOURCE[0]}の代わりに$0を使用しても、スクリプトが<name>またはsource <name>のどちらとして呼び出されたかにかかわらず、同じ動作になります。

148
Andrew Norrie

私は今日この問題をもう一度見直さなければならず、スクリプト自体の中からBashスクリプトのソースディレクトリを取得する

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

リンクされた回答にはもっと多くの変形があります。スクリプト自体がシンボリックリンクの場合。

40
Felix Rabe

シェルスクリプトの絶対パスを取得する

Readlinkで-fオプションを使用していないので、BSD/Mac OS Xで動作するはずです。

サポート

  • source ./script(.ドット演算子によって呼び出された場合)
  • 絶対パス/ path/to/script
  • ./scriptのような相対パス
  • /path/dir1/../dir2/dir3/../script
  • シンボリックリンクから呼び出されたとき
  • シンボリックリンクがネストされている場合)(例)foo->dir1/dir2/bar bar->./../doe doe->script
  • 呼び出し元がスクリプト名を変更したとき

私はこのコードがうまくいかないコーナーケースを探しています 。私にお知らせください。

コード

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"

知られている

スクリプト はディスクのどこかにある必要があります 。それがネットワーク上にあるようにしましょう。 PIPEからこのスクリプトを実行しようとするとうまくいきません

wget -o /dev/null -O - http://Host.domain/dir/script.sh |bash

技術的に言えば、それは未定義です。実際には、これを検出するための正しい方法はありません。 (コプロセスは親の環境にアクセスできません。)

36
GreenFox

つかいます:

SCRIPT_PATH=$(dirname `which $0`)

whichは、渡された引数がシェルプロンプトで入力されたときに実行されたであろう実行可能ファイルのフルパスを標準出力に表示します($ 0が含むものです)

dirnameは、ディレクトリ名以外のサフィックスをファイル名から削除します。

したがって、パスが指定されているかどうかにかかわらず、スクリプトのフルパスになります。

31
Matt

私のLinuxシステムではデフォルトでrealpathがインストールされていないので、私には次のように動作します。

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPTはスクリプトへの実際のファイルパスを含み、$SCRIPTPATHはスクリプトを含むディレクトリへの実際のパスを含みます。

これを使う前に この答え のコメントを読んでください。

25
ypid

読みやすい?以下は代替案です。シンボリックリンクは無視されます

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
)

echo -n "current "
pwd
echo script $currentDir

私は数年前に上記の答えを投稿して以来、私は自分のプラクティスを適切にシンボリックリンクを処理するこのLinux特有のパラダイムを使用するように進化させました:

Origin=$(dirname $(readlink -f $0))
13
gerardw

この質問に非常に遅く答えるが、私は使用します:

SCRIPT=$( readlink -m $( type -p $0 ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked
11
Stormcloud

私たちはGitHubに自社製品 realpath-lib を配置して、無料で障害のないコミュニティでの使用を目指しています。

恥知らずなプラグしかしこのBashライブラリであなたはできる:

get_realpath <absolute|relative|symlink|local file>

この関数はライブラリの中核です。

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

外部の依存関係は必要ありません。Bash4+だけです。 get_dirnameget_filenameget_stemnameおよび validate_path validate_realpath。それは無料で、きれいで、単純でそしてよく文書化されています、それでそれは学習目的のためにも使われることができます、そして間違いなく改善されることができます。プラットフォームを越えて試してみてください。

更新:いくつかのレビューとテストの後、上記の関数を同じ結果(dirnameを使わずに、純粋なBashのみ)を実現するものに置き換えましたが、より効率的です:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

これには、物理​​システムへのシンボリックリンクを解決する機能を提供する環境設定no_symlinksも含まれます。デフォルトではシンボリックリンクをそのまま残します。

8
AsymLabs

単に:

BASEDIR=$(readlink -f $0 | xargs dirname)

派手な演算子は必要ありません。

7
poussma

Bourne Shellsh)準拠の方法:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
5
Haruo Kinoshita

次の変数を定義しようとします。

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"

あるいは、Bashで以下の機能を試すことができます。

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

この関数は1つの引数を取ります。引数がすでに絶対パスを持っている場合は、それをそのまま出力します。それ以外の場合は、$PWD変数+ファイル名引数(./プレフィックスなし)を出力します。

関連する

5
kenorb

承認された解決策は(私にとっては)「ソース対応」ではないという不都合があります。
"source ../../yourScript"から呼び出した場合、$0は "bash"になります。

次の関数(bash> = 3.0の場合)から正しいパスが得られますが、スクリプトが呼び出されることがあります(直接またはsourceを介して、絶対パスまたは相対パスで)。
(「正しいパス」とは、 呼び出されるスクリプトの絶対パス 、別のパスから直接呼び出される場合でも、直接または「source」で呼び出される場合でも)

#!/bin/bash
echo $0 executed

function bashscriptpath() {
  local _sp=$1
  local ascript="$0"
  local asp="$(dirname $0)"
  #echo "b1 asp '$asp', b1 ascript '$ascript'"
  if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
  Elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
  else
    if [[ "$ascript" == "bash" ]] ; then
      ascript=${BASH_SOURCE[0]}
      asp="$(dirname $ascript)"
    fi  
    #echo "b2 asp '$asp', b2 ascript '$ascript'"
    if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
    Elif [[ "${ascript#../}" != "$ascript" ]]; then
      asp=$(pwd)
      while [[ "${ascript#../}" != "$ascript" ]]; do
        asp=${asp%/*}
        ascript=${ascript#../}
      done
    Elif [[ "${ascript#*/}" != "$ascript" ]];  then
      if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
    fi  
  fi  
  eval $_sp="'$asp'"
}

bashscriptpath H
export H=${H}

重要なのは、 "source"の場合を検出し、実際のスクリプトを取り戻すために${BASH_SOURCE[0]}を使用することです。

3
VonC

この問題についてもう一度考えてみましょう。このスレッド内で参照されているものとして、Origin here を持つ非常に一般的な解決策があります。

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

私はdirnameを使用しているため、この解決策を避けました。特にセキュリティ上の理由からスクリプトをロックする必要がある場合は、プラットフォーム間で問題が生じる可能性があります。しかし、純粋なBashの代替手段として、どのように使用しますか。

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

これは選択肢でしょうか。

3
AsymLabs

おそらく、次の質問に対する一般的な回答が役立つことがあります。

MacでGNUのreadlink -fの動作を取得する方法を教えてください。

$ PWDと$ 0を連結したときに得られる名前を正規化したいだけなのであれば($ 0は絶対的ではないと仮定して)、abs_dir=${abs_dir//\/.\//\/}などの行に沿った一連の正規表現の置き換えを使用します。

はい、私はそれが恐ろしいように見えますが、それはうまくいくでしょう、そして純粋なBashです。

3
wich

Bashを使用する場合、外部コマンドへの呼び出しを必要としないので、これが最も便利な方法だと思います。

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
2
Nordlöw

これを試して:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
1
diyism

まさにそのために、純粋にテキスト形式で、純粋にBashで処理を行うスクリプトを少しハッキングしました。すべてのEdgeケースをキャッチできたらと思います。

私が他の答えで言及した${var//pat/repl}は、最短一致のみを置き換えることはできないため機能しません。これは、/foo/../をたとえば/*/../は、単一のエントリではなく、その前にあるすべてのものを取得します。そして、これらのパターンは実際には正規表現ではないので、どのように機能させることができるのかわかりません。だから、ここで私が思いついた、うまく複雑なソリューションをお楽しみください。 ;)

ところで、未処理のEdgeケースが見つかった場合はお知らせください。

#!/bin/bash

canonicalize_path() {
  local path="$1"
  OIFS="$IFS"
  IFS=$'/'
  read -a parts < <(echo "$path")
  IFS="$OIFS"

  local i=${#parts[@]}
  local j=0
  local back=0
  local -a rev_Canon
  while (($i > 0)); do
    ((i--))
    case "${parts[$i]}" in
      ""|.) ;;
      ..) ((back++));;
      *) if (($back > 0)); then
           ((back--))
         else
           rev_Canon[j]="${parts[$i]}"
           ((j++))
         fi;;
    esac
  done
  while (($j > 0)); do
    ((j--))
    echo -n "/${rev_Canon[$j]}"
  done
  echo
}

canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
0
wich

ワンライナー

`dirname $(realpath $0)`
0
Abhijit

私はしばらくの間(OS Xではなく)次のアプローチを成功裏に使用してきました、そしてそれはシェル組み込みのみを使用して、私が見た限りで 'source foobar.sh'ケースを取り扱います。

以下の(急いでまとめた)サンプルコードの問題の1つは、関数が$ PWDを使用していることです。これは、関数呼び出し時には正しくない場合と正しくない場合があります。だからそれを処理する必要があります。

#!/bin/bash

function canonical_path() {
  # Handle relative vs absolute path
  [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
  # Change to dirname of x
  cd ${x%/*}
  # Combine new pwd with basename of x
  echo $(pwd -P)/${x##*/}
  cd $OLDPWD
}

echo $(canonical_path "${BASH_SOURCE[0]}")

type [
type cd
type echo
type pwd
0
andro