bash - 表示 - シェルスクリプト パス指定




内部からBashスクリプトのソースディレクトリを取得する (20)

スクリプト内に Bashスクリプトが置かれているディレクトリのパスを取得するにはどうすればよいですか?

たとえば、別のアプリケーションのランチャーとしてBashスクリプトを使用したいとします。 私は作業ディレクトリをBashスクリプトが置かれているディレクトリに変更したいので、そのディレクトリのファイルを操作することができます:

$ ./application

$ BASH_SOURCEを使うことができます

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

#!/ bin / shではなく#!/ bin / bashを使用する必要があることに注意してください。


GNU coreutils readlinkを持つシステム(例えば、linux):

$(readlink -f "$(dirname "$0")")

$0にスクリプトのファイル名が含まれている場合、 BASH_SOURCEを使用する必要はありません。


dirnameコマンドは最も基本的なもので、$ 0(スクリプト名)変数からファイル名までのパスを解析するだけです:

dirname "$0"

しかし、 マットbが指摘するように、返されるパスは、スクリプトの呼び出し方法によって異なります。 pwdは、現在のディレクトリが何であるかを伝えるだけで、スクリプトが存在するディレクトリではないことを伝えるだけなので、仕事をしません。さらに、スクリプトへのシンボリックリンクが実行されると、(おそらく相対的な)パス実際のスクリプトではなくリンクが存在する場所に移動します。

readlinkコマンドについて言及した人もいますが、最も簡単な方法は次のとおりです:

dirname "$(readlink -f "$0")"

readlinkは、スクリプトパスをファイルシステムのルートからの絶対パスに解決します。 したがって、単一または二重ドット、ティルダ、シンボリックリンクを含むパスは完全パスに解決されます。

ここに、 whatdir.shのそれぞれを示すスクリプトがあります

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

私のホームディレクトリでこのスクリプトを実行し、相対パスを使用する:

>>>$ ./whatdir.sh 
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

繰り返しますが、スクリプトへのフルパスを使用します。

>>>$ /Users/phatblat/whatdir.sh 
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

今すぐディレクトリを変更する:

>>>$ cd /tmp
>>>$ ~/whatdir.sh 
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

最後に、シンボリックリンクを使用してスクリプトを実行します。

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

$_は、$ 0の代替として言及する価値がある。 bashからスクリプトを実行している場合、受け入れられる答えは以下のように短縮されます:

DIR="$( dirname "$_" )"

これは、スクリプトの最初の文でなければなりません。


pwdを使用して現在の作業ディレクトリを見つけることができますpwdは特定のファイルのディレクトリ(実行されたコマンドは$0dirname $0は現在のスクリプトのディレクトリを指定します)を探します。

しかし、 dirnameはファイル名のディレクトリ部分を正確に示しています。これは、現在の作業ディレクトリに対して相対的になる可能性が高くなります。 スクリプトが何らかの理由でディレクトリを変更する必要がある場合、 dirnameからの出力は無意味になります。

私は以下を提案します:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

これにより、相対ディレクトリではなく絶対パスが取得されます。

スクリプトは別のbashインスタンスで実行されるため、後で作業ディレクトリを復元する必要はありませんが、何らかの理由でスクリプトを元に戻したい場合は、前に変数にpwdの値を簡単に割り当てることができます将来の使用のためにディレクトリを変更します。

ただ

cd `dirname $0`

問題の特定のシナリオを解決すると、私は絶対的な道がより一般的にもっと有用であることがわかります。


ここに、簡単で正しい方法があります:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

説明:

  • ${BASH_SOURCE[0]} - スクリプトへのフルパス。 この値は、スクリプトがソースされている場合でも正しいです。たとえば、 source <(echo 'echo $0')bashを出力し、 ${BASH_SOURCE[0]}と置き換えるとスクリプトのフルパスを出力します。 (もちろん、これはあなたがBashに依存していることを前提としています)。

  • readlink -f - 指定されたパスのシンボリックリンクを再帰的に解決します。 これはGNU拡張であり、(例えば)BSDシステムでは利用できません。 あなたがMacを使っているなら、Homebrewを使ってGNU coreutilsをインストールし、これをgreadlink -fことができます。

  • もちろん、 dirnameはパスの親ディレクトリを取得します。


これはLinux固有のものですが、以下を使用することができます:

SELF=$(readlink /proc/$$/fd/255)

これはbash-3.2で動作します:

path="$( dirname "$( which "$0" )" )"

その使用例を以下に示します。

あなたが持っていると言う、〜/ビン、あなたの中にあるディレクトリ、$のPATHを。このディレクトリ内にスクリプトAがあります。これは、スクリプト〜/ bin / lib / Bのソースです。インクルードされたスクリプトがオリジナルのもの(サブディレクトリlib)を基準にした相対的な場所は知っていますが、ユーザの現在のディレクトリからの相対的な場所はわかりません。

これは、以下の(Aの内部で)解決されます。

source "$( dirname "$( which "$0" )" )/lib/B"

ユーザーがどこにいるか、どのようにスクリプトを呼び出すかは問題ではありません。これはいつも有効です。


これはそれを行う必要があります:

DIR=$(dirname "$(readlink -f "$0")")

シンボリックリンクとパスのスペースで動作します。 dirnamereadlinkのマニュアルページを参照してください。

編集:

コメントトラックからは、Mac OSでは動作しないようです。 私はそれがなぜなのか分かりません。 助言がありますか?


これらのどれも、OS XのFinderによって起動されたbashスクリプトでは機能しませんでした。

SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"

綺麗ではありませんが、仕事は終わりです。


以下を使用してください。

real=$(realpath $(dirname $0))

次の相互互換性のあるソリューションを試してください。

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

realpathまたはreadlink常に利用可能ではないコマンド(オペレーティングシステムに応じて)と${BASH_SOURCE[0]}のみbashシェルで利用可能です。

あるいは、bashで以下の関数を試すこともできます:

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

この関数は1つの引数をとります。引数に絶対パスがある場合はそれをそのまま出力し、そうでなければ$PWD変数+ファイル名の引数を./プレフィックスなしで出力します。

関連:


私の見解では、最適なコンパクトなソリューションは次のようになります。

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

Bash以外のものに依存することはありません。使用dirnamereadlinkそしてbasename最終的には互換性の問題につながるので、彼らは最高の場合は可能な限り避けられます。


私はこのようなものを使用します:

# retrieve the full pathname of the called script
scriptPath=$(which $0)

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi

私はこれらのすべてを試してみましたが、どれも働いていませんでした。 1つは非常に近いですが、ひどく壊れた小さなバグがありました。 パスを引用符で囲むのを忘れてしまった。

また、多くの人がシェルからスクリプトを実行していると仮定しているため、新しいスクリプトを開いたときにあなたのホームにデフォルト設定されていることを忘れてしまいます。

サイズについては、このディレクトリを試してください:

/ var / No / Thought /ディレクトリ/名前/場所にあるスペースについて/あなたのfile.textはここにあります

これは、どこでどのように実行するかにかかわらず、正しい結果を得ます。

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

ですから実際には便利ですが、ここでは実行中のスクリプトのディレクトリに変更する方法があります:

cd "`dirname "$0"`"

希望が役立つ


私は与えられた答えの多くを比較し、よりコンパクトな解決策を考え出しました。 これらはあなたの好きな組み合わせから生じる狂ったエッジケースのすべてを処理するようです:

  • 絶対パスまたは相対パス
  • ファイルとディレクトリのソフトリンク
  • scriptbash scriptbash -c scriptsource script 、またはbash scriptとしての呼び出し. script . script
  • ディレクトリやファイル名のスペース、タブ、改行、ユニコードなど
  • ハイフンで始まるファイル名

Linuxから実行している場合は、現在実行中のスクリプトの完全に解決されたソースを見つけるための最良の解決策です(対話型セッションでは、リンクはそれぞれ/dev/pts/X指しています)。

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

これには少しの醜さがありますが、修正はコンパクトで分かりやすくなっています。 私たちはbashプリミティブのみを使用していませんが、 readlinkがタスクをかなり単純化するので、私はそれで大丈夫です。 echo Xは変数文字列の末尾にXを追加するので、ファイル名の末尾にある空白は消えず、行末のパラメータ置換${VAR%X}Xを取り除きます。 readlinkはそれ自身の改行を追加するので(以前のトリッキーではないにしても、コマンド置換で通常は食べられるでしょう)、それを取り除かなければなりません。 これは\nなどのエスケープシーケンスを使用して改行を表現できるようにする$''クォート方式を使用することで最も簡単に実現できます(これは簡単に名前を付けられたディレクトリやファイルを簡単に作る方法です)。

上記では、現在実行中のスクリプトをLinux上に置く必要性について説明しますが、 procファイルシステムを自由に使えない場合や、他のファイルの完全解決されたパスを見つけようとしている場合は、下のコードが役に立ちます。 これは上記の1つのライナーからのわずかな変更です。 奇妙なディレクトリ/ファイル名で遊んでいるなら、 lsreadlink両方で出力をチェックすることは参考になりますreadlinkは "単純化された"パスを出力して、代わりに? 改行のようなもののために。

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

うーん、もし、パス名basename&dirnameがそれをカットせず、パスを歩くのは難しいです(親がPATHをエクスポートしなかったらどうでしょうか?)。しかし、シェルはそのスクリプトに対してオープンハンドルを持っていなければならず、bashではハンドルは#255です。

SELF=`readlink /proc/$$/fd/255`

私のために働く。


#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`

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

スクリプトがどこから呼び出されても、スクリプトの完全なディレクトリ名を与える便利な1行です。

スクリプトを見つけるために使用されるパスの最後のコンポーネントがシンボリックリンクでない限り(ディレクトリリンクはOKです)、動作します。 スクリプト自体へのリンクも解決したい場合は、複数行のソリューションが必要です。

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null && pwd )"

最後のものはエイリアス、 sourcebash -c 、シンボリックリンクなどの任意の組み合わせで動作します。

注意:このスニペットを実行する前に別のディレクトリに移動すると、結果が正しくない可能性があります。 また、 $CDPATH gotchasに注意してください

どのように動作するかを理解するには、このより冗長な形式を実行してみてください。

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

そして、次のような印字をします:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

SCRIPT_DIR=$( cd ${0%/*} && pwd -P )




directory