Unixシェルスクリプトは、スクリプトファイルが存在するディレクトリを見つけますか?


511

基本的に、シェルスクリプトファイルの場所に関連するパスを使用してスクリプトを実行する必要があります。現在のディレクトリを、スクリプトファイルが存在するディレクトリと同じディレクトリに変更するにはどうすればよいですか?


12
本当に重複していますか?この質問は「UNIXシェルスクリプト」に関するもので、もう1つは特にBashに関するものです。
michaeljt 2015年

2
@BoltClock:この質問は不適切に閉じられました。リンクされている質問はBashに関するものです。この質問は、Unixシェルプログラミングについてです。受け入れられた答えはまったく異なることに注意してください!
ディートリッヒエップ2016年

@Dietrich Epp:その通りです。質問者が受け入れた回答を選択し、[おそらくbash]タグを追加したことで(おそらくそれに応じて)、フラグに応じて質問を重複としてマークするようになりました。
BoltClock

2
私はこの答えの方がいいと思います
。Bash

回答:


568

Bashでは、次のようにして必要なものを取得する必要があります。

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

17
別のディレクトリのシンボリックリンクを介してスクリプトを呼び出した場合、これは機能しません。その作業を行うにはreadlink、同様に使用する必要があります(以下のalの回答を参照)
AndrewR

44
bashでは$BASH_SOURCE、の代わりに使用する方が安全です。スクリプトに「ソーシング」するときなど、呼び出されるスクリプトのパスが常に含まれる$0$0は限らないためです。
mklement0

2
$BASH_SOURCEBash固有であり、問​​題はシェルスクリプト全般に関するものです。
Ha-Duong Nguyen 2014年

7
@auraham:CUR_PATH=$(pwd)またはpwd、現在のディレクトリを返します(スクリプトの親ディレクトリである必要はありません)。
Andreas Dietrich

5
私は@ mklement0が推奨するメソッドをを使用して試しました$BASH_SOURCE。私のスクリプトは、別のスクリプトから呼び出され、されて$0戻り.ながら、$BASH_SOURCEリターン(私の場合は、右のサブディレクトリscripts)。
David Rissato Cruz、2015

402

元の投稿にはソリューションが含まれています(応答は無視してください。役立つものは何も追加されていません)。興味深い作業はreadlink、オプション付きの前述のunixコマンドによって行われます-f。スクリプトが絶対パスと相対パスの両方で呼び出されたときに機能します。

bash、sh、kshの場合:

#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

tcsh、cshの場合:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

参照:https : //stackoverflow.com/a/246128/59087


12
注:すべてのシステムにがあるわけではありませんreadlink。それが、私がpushd / popd(bashに組み込まれている)の使用を推奨した理由です。
docwhat '20年

23
-fオプションは、readlinkOS X(ライオン)上に異なる何かをすると、おそらくBSD。stackoverflow.com/questions/1055671/…–
エルグン

9
@Ergwunのコメントを明確にするため:-f(Lionの時点では)OS Xではまったくサポートされていません。そこにドロップし-fて、せいぜい1レベルの間接参照で解決するpushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"か、リンクされた投稿に示されているように、独自の再帰的なsymlink-followingスクリプトをロールすることができます。
mklement0

2
OPが絶対パスを必要とする理由はまだわかりません。「。」を報告しています アクセスしたい場合は大丈夫動作するべきことは、スクリプトのパスからの相対ファイル、あなたは./myscript.shのようなスクリプトと呼ばれる
ステファンHaberl

10
@StefanHaberl私はあなたの現在の作業ディレクトリは、例えば、スクリプトの場所(と異なっていた間、あなたはスクリプトを実行した場合、それが問題になると思いsh /some/other/directory/script.sh)、この場合には、.あなたのPWD、ではないでしょう/some/other/directory
ジョンzの

51

回答に関する以前のコメントはそれを言っていましたが、他のすべての回答の中で見落とすのは簡単です。

bashを使用する場合:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bashリファレンスマニュアル、5.2 Bash変数


3
これだけが環境パスのスクリプトで機能し、最も投票されたものは機能しません。ありがとうございました!
7月

dirname "$BASH_SOURCE"代わりにを使用して、$ BASH_SOURCEのスペースを処理する必要があります。
Mygod

1
ShellCheckツールによると、ディレクトリをより明確に出力する方法は次のようになります。 "$(dirname" $ {BASH_SOURCE {0}} ")" BASH_SOURCEは配列であり、添え字がない場合、デフォルトで最初の要素が使用されます。 。
エイドリアン

1
@AdrianM。、あなたはいないインデックスのために、ブレース、ブラケットをしたい:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown

私にとってはダメです。ドットだけを印刷します(つまり、おそらく現在のディレクトリ)
JL_SO

40

あなたがbashを使用していると仮定します

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

このスクリプトは、現在のディレクトリを出力し、次にスクリプトが存在するディレクトリを出力する必要があります。たとえば/、のスクリプトでから呼び出すと、/home/mez/出力されます

/
/home/mez

コマンドの出力から変数を割り当てるときは、コマンドを$(andで)囲みます。そうしないと、目的の出力が得られません。


3
現在のディレクトリからスクリプトを呼び出すと、これは機能しません。
Eric Wang

1
@EricWangあなたは常に現在のディレクトリにいます。
ctrl-alt-delor 2017

私にとって、$ current_dirは実際にスクリプトの呼び出し元のパスです。ただし、$ script_dirはスクリプトのディレクトリではなく、単なるドットです。
マイケル

31

bashを使用している場合...

#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"

4
あなたは置き換えることができますpushd/をpopdcd $(dirname "${0}")し、cd -彼らが持っている場合、それは、他のシェル上で動作させるためにpwd -L
docwhat

なぜここでpushdとpopdを使用するのですか?
qodeninja 14年

1
したがって、元のディレクトリを変数に格納する必要はありません。関数などでよく使うパターンです。それは本当によく入れ子になり、それは良いことです。
docwhat 14年

変数がスクリプトで参照されているかどうかに関係なく、変数はメモリ内にまだ保存されています。また、pushdとpopdを実行するコストは、スクリプトでローカルBash変数を作成しないことによる節約(CPUサイクルと読みやすさの両方)をはるかに上回ると思います。
ingyhere

20

theMarkoが示唆するように:

BASEDIR=$(dirname $0)
echo $BASEDIR

これは、スクリプトが存在するのと同じディレクトリからスクリプトを実行しない限り機能します。その場合、値は「。」になります。

その問題を回避するには、以下を使用します。

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

これで、スクリプト全体で変数current_dirを使用して、スクリプトディレクトリを参照できます。ただし、これにはまだsymlinkの問題がある可能性があります。


20

この質問に対する最良の回答はここで回答されました:
内部からのBashスクリプトのソースディレクトリの取得

そしてそれは:

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

どこから呼び出されたかに関係なく、スクリプトの完全なディレクトリ名を提供するワンライナー。

仕組みを理解するには、次のスクリプトを実行します。

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


10

それをPOSIXワンライナーにしましょう:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

BSDシェルを含む多くのBourne互換シェルでテストされています。

私の知る限り、私は作者であり、パブリックドメインに入れました。詳細については、https//www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/を参照して ください。


1
cd: too many argumentsパスにスペースが含まれている場合は、記述どおりにを返します$PWD。(明らかな修正ですが、実際に存在するエッジケースの数を示すだけです)
マイケル

1
賛成票を投じます。パスにスペースがあると失敗するという@michaelからのコメントを除いて...それに対する修正はありますか?
スペクター、

1
@spechterはい、そのための修正があります。更新を参照してくださいjasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
JANSáreník

@Der_Meister-より具体的にしてください。または書き込み(そしておそらく暗号化)jasan.tkでjasanへのメール
JANSáreník

2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2(代わりに元のスクリプトへのパスを表示する必要があります)
Der_Meister

10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"

3
私が知っている最も信頼できる非bash固有の方法。
eddygeek、

10

実際のスクリプトディレクトリを取得したい場合(シンボリックリンクを使用してスクリプトを呼び出しているか直接呼び出しているかに関係なく)、次のことを試してください。

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

これはlinuxとmacOSの両方で機能します。ここで言及してrealpathいる人は誰もいませんでした。このアプローチに欠点があるかどうかはわかりません。

macOSでは、coreutilsを使用するためにインストールする必要がありますrealpath。例:brew install coreutils


6

前書き

この回答、このスレッド(TheMarkoによって作成された)の非常に壊れているが衝撃的なトップ投票の回答を修正します。

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

なぜdirname "$ 0"を使用できないのですか?

dirname $ 0は、ユーザーが非常に特殊な方法でスクリプトを起動した場合にのみ機能します。この回答が失敗してスクリプトがクラッシュする状況をいくつか見つけることができました。

まず、この答えがどのように機能するかを理解しましょう。彼はスクリプトディレクトリを取得しています。

dirname "$0"

$ 0は、スクリプトを呼び出すコマンドの最初の部分を表します(基本的には、引数なしで入力されたコマンドです。

/some/path/./script argument1 argument2

$ 0 = "/ some / path /./ script"

dirnameは基本的に文字列内の最後の/を見つけ、そこで切り捨てます。だからあなたがするなら:

  dirname /usr/bin/sha256sum

取得します:/ usr / bin

/ usr / bin / sha256sumは適切にフォーマットされたパスですが、この例はうまく機能しますが

  dirname "/some/path/./script"

うまくいかず、あなたに与えるでしょう:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

スクリプトと同じディレクトリにいて、このコマンドで起動するとします

./script   

この状況での$ 0は./scriptで、dirname $ 0は次のようになります。

. #or BASEDIR=".", again this will crash your script

使用:

sh script

フルパスを入力しないと、BASEDIR = "。"も表示されます。

相対ディレクトリを使用する:

 ../some/path/./script

dirnameに$ 0を与えます:

 ../some/path/.

/ someディレクトリにいて、この方法でスクリプトを呼び出した場合(最初に/がないことに注意してください。ここでも相対パスです):

 path/./script.sh

dirname $ 0に対してこの値を取得します。

 path/. 

および./path/./script(相対パスの別の形式)は、次のようになります。

 ./path/.

basedir $ 0が機能する唯一の2つの状況は、ユーザーがshまたはtouchを使用してスクリプトを起動する場合です。どちらも$ 0になるためです。

 $0=/some/path/script

これは、dirnameで使用できるパスを提供します。

ソリューション

上記のすべての状況を考慮して検出し、問題が発生した場合は修正を適用します。

#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.

#set to false to not see all the echos
debug=true

if [ "$debug" = true ]; then echo "\$0=$0";fi


#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                     #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                     #situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi

4

このワンライナーは、シェルスクリプトがどこにあるのか、それを実行したのか、それを調達したのかは関係ありません。また、関連するすべてのシンボリックリンクを解決します(該当する場合)。

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

ちなみに、あなたは/ bin / bashを使っていると思います。


2

非常に多くの答え、すべてがもっともらしい、それぞれが賛否両論であり、目的がわずかに異なる(おそらくそれぞれについて述べる必要がある)。これは、すべてのシステムで、すべてのbash(bashのバージョン、readlinkまたはpwdオプションについての想定なし)で明確かつ機能するという主な目的を満たし、期待どおりに動作する(シンボリックリンクの解決は興味深い問題ですが、通常は実際には必要ありません)、パス内のスペースなどのエッジケースを処理し、エラーを無視し、問題がある場合は正しいデフォルトを使用します。

各コンポーネントは、個別に使用できる個別の変数に格納されます。

# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}      # this script's name
PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"


-4

これでうまくいくはずです:

echo `pwd`/`dirname $0`

呼び出された方法とcwdによっては醜く見えるかもしれませんが、どこに行く必要があるかがわかります(または、文字列がどのように見えるか気になっている場合は、文字列を微調整できます)。


1
ここでのstackoverflowエスケープ問題:確かに次のようになるはずです。`pwd`/`dirname $0`ただし、シンボリックリンクで失敗する可能性があります
Andreas Dietrich 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.