Bashスクリプトが配置されているディレクトリのパスを、そのスクリプト内で取得するにはどうすればよいですか?
別のアプリケーションのランチャーとしてBashスクリプトを使用したいと思います。作業ディレクトリをBashスクリプトが配置されているディレクトリに変更したいので、次のようにそのディレクトリ内のファイルを操作できます。
$ ./application
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
どこから呼び出されても、スクリプトの完全なディレクトリ名を提供する便利なワンライナーです。
スクリプトを見つけるために使用されるパスの最後のコンポーネントがシンボリック リンクでない限り、機能します (ディレクトリ リンクは問題ありません)。スクリプト自体へのリンクも解決したい場合は、複数行のソリューションが必要です。
#!/usr/bin/env bash
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 2>&1 && 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 2>&1 && pwd )
source
この最後のものは、エイリアスbash -c
、、、、シンボリックリンクなどの任意の組み合わせで機能します。
注意:このスニペットを実行cd
する前に別のディレクトリに移動すると、結果が正しくない可能性があります。
また、ユーザーが出力を stderr にリダイレクトするように cd をスマートにオーバーライドした場合 ( Mac で呼び出す場合などのエスケープ シーケンスを含む)、$CDPATH
gotchasと stderr 出力の副作用に注意してください。コマンドの最後にupdate_terminal_cwd >&2
追加すると、両方の可能性が処理されます。>/dev/null 2>&1
cd
それがどのように機能するかを理解するには、次のより詳細なフォームを実行してみてください。
#!/usr/bin/env 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" )" >/dev/null 2>&1 && 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'
使用dirname "$0"
:
#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"
pwd
スクリプトが含まれているディレクトリからスクリプトを実行していない場合、単独で使用しても機能しません。
[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
このdirname
コマンドは最も基本的なもので、$0
(スクリプト名) 変数からファイル名までのパスを解析するだけです。
dirname "$0"
しかし、matt bが指摘したように、返されるパスは、スクリプトの呼び出し方法によって異なります。pwd
スクリプトが存在するディレクトリではなく、現在のディレクトリが何であるかのみを示すため、ジョブを実行しません。さらに、スクリプトへのシンボリックリンクが実行された場合、への(おそらく相対)パスを取得します実際のスクリプトではなく、リンクが存在する場所。
他の人はコマンドについて言及していreadlink
ますが、最も簡単には、次を使用できます。
dirname "$(readlink -f "$0")"
readlink
スクリプト パスをファイル システムのルートからの絶対パスに解決します。そのため、1 つまたは 2 つのドット、チルダ、および/またはシンボリック リンクを含むパスは、フル パスに解決されます。
これらのそれぞれを示すスクリプトを次に示します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
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
while([ -h "${SCRIPT_PATH}" ]); do cd "$(dirname "$SCRIPT_PATH")";
SCRIPT_PATH=$(readlink "${SCRIPT_PATH}"); done
fi
cd "$(dirname ${SCRIPT_PATH})" > /dev/null
SCRIPT_PATH=$(pwd);
popd > /dev/null
を含むすべてのバージョンで機能します。
source
コマンド " " 別名.
(ドット) 演算子によってスクリプトが呼び出されたとき。$0
が呼び出し元から変更されたとき。"./script"
"/full/path/to/script"
"/some/path/../../another/path/script"
"./some/folder/script"
または、Bash スクリプト自体が相対シンボリック リンクである場合は、それをたどり、リンク先のスクリプトのフル パスを返します。
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do cd "$(dirname "$SCRIPT_PATH")"; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd "$(dirname ${SCRIPT_PATH})" > /dev/null
SCRIPT_PATH=$(pwd);
popd > /dev/null
SCRIPT_PATH
どのように呼び出されても、フルパスで指定されます。
スクリプトの開始時にこれを見つけてください。
使用できます$BASH_SOURCE
:
#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`
#!/bin/bash
and は#!/bin/sh
Bash 拡張機能であるため、使用する必要があることに注意してください。
pwd
現在の作業ディレクトリdirname
を検索したり、特定のファイルのディレクトリを検索したりするために使用できます (実行されたコマンドは$0
であるためdirname $0
、現在のスクリプトのディレクトリが表示されます)。
ただし、dirname
ファイル名のディレクトリ部分を正確に示します。これは、現在の作業ディレクトリに関連している可能性が高いです。何らかの理由でスクリプトがディレクトリを変更する必要がある場合、からの出力dirname
は無意味になります。
次のことをお勧めします。
#!/bin/bash
reldir=`dirname $0`
cd $reldir
directory=`pwd`
echo "Directory is $directory"
このようにして、相対ディレクトリではなく絶対ディレクトリを取得します。
スクリプトは別の Bash インスタンスで実行されるため、後で作業ディレクトリを復元する必要はありませんが、何らかの理由でスクリプトを元に戻したい場合はpwd
、変数に値を簡単に割り当てることができます。将来の使用のために、ディレクトリを変更する前に。
ただ
cd `dirname $0`
質問の特定のシナリオを解決します。一般的に、より便利な方法への絶対パスがあることがわかりました。
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
これは、他の人が成功させたほど簡単ではないと思います。 pwd
現在のディレクトリは必ずしもスクリプトのあるディレクトリではないため、機能しません。 $0
常に情報を持っているわけではありません。スクリプトを呼び出すには、次の 3 つの方法を検討してください。
./script
/usr/bin/script
script
1 番目と 3 番目の方法で$0
は、フル パス情報がありません。2番目と3番目では、pwd
機能しません。3 番目の方法でディレクトリを取得する唯一の方法は、パスを実行して、正しい一致を持つファイルを見つけることです。基本的に、コードは OS が行うことをやり直す必要があります。
あなたが求めていることを行う1つの方法は、/usr/share
ディレクトリ内のデータをハードコーディングし、フルパスで参照することです。とにかく、データは/usr/bin
ディレクトリにあるべきではないので、これはおそらく行うべきことです。
$(dirname "$(readlink -f "$BASH_SOURCE")")
これは、 Mac OS X v10.6.6 (Snow Leopard)の現在の作業ディレクトリを取得します。
DIR=$(cd "$(dirname "$0")"; pwd)
これは Linux 固有のものですが、次のものを使用できます。
SELF=$(readlink /proc/$$/fd/255)
以下は POSIX 準拠のワンライナーです。
SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH
#!/bin/sh
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
PRG=`readlink "$PRG"`
done
scriptdir=`dirname "$PRG"`
私はこれらすべてを試しましたが、どれもうまくいきませんでした。1つは非常に近いものでしたが、小さなバグがあり、ひどく壊れていました。パスを引用符で囲むのを忘れていました。
また、多くの人は、スクリプトをシェルから実行していると想定しているため、新しいスクリプトを開いたときに、デフォルトで自宅に設定されていることを忘れています。
サイズについては、このディレクトリを試してください。
/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text
これにより、実行方法や場所に関係なく正しくなります。
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"
したがって、実際に役立つように、実行中のスクリプトのディレクトリに変更する方法は次のとおりです。
cd "`dirname "$0"`"
簡単で正しい方法は次のとおりです。
actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")
説明:
${BASH_SOURCE[0]}
- スクリプトへのフル パス。この値は、スクリプトがソースされている場合でも正しくなります。たとえば、bashsource <(echo 'echo $0')
が出力されますが、これを に置き換えると、スクリプトのフル パスが出力されます。(もちろん、これはあなたが Bash に依存しても問題ないことを前提としています。)${BASH_SOURCE[0]}
readlink -f
- 指定されたパス内のシンボリック リンクを再帰的に解決します。これは GNU 拡張機能であり、(たとえば) BSD システムでは使用できません。Mac を実行している場合は、Homebrew を使用して GNU をインストールcoreutils
し、これをgreadlink -f
.
もちろんdirname
、パスの親ディレクトリを取得します。
これは、ソリューション e-satis と 3bcdnlklvc04aが回答で指摘したものを少し修正したものです。
SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
SCRIPT_DIR="$PWD"
popd > /dev/null
}
これは、リストされているすべてのケースで引き続き機能するはずです。
popd
これにより、失敗した後に防止されpushd
ます。konsolebox に感謝します。
GNU coreutils を備えたシステムreadlink
(Linux など) の場合:
$(readlink -f "$(dirname "$0")")
whenにスクリプト ファイル名が含まれているBASH_SOURCE
場合は、使用する必要はありません。$0
私は次のようなものを使用します:
# 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
使用してみてください:
real=$(realpath "$(dirname "$0")")
$_
の代替として言及する価値があり$0
ます。Bash からスクリプトを実行している場合、受け入れられた回答は次のように短縮できます。
DIR="$( dirname "$_" )"
これは、スクリプトの最初のステートメントでなければならないことに注意してください。
これは Bash 3.2 で機能します。
path="$( dirname "$( which "$0" )" )"
~/bin
にディレクトリがある場合は、このディレクトリ内$PATH
にあります A
。スクリプトのソース~/bin/lib/B
です。含まれているスクリプトがサブディレクトリ内の元のスクリプトに対して相対的な場所を知っていますがlib
、ユーザーの現在のディレクトリに対して相対的な場所はわかりません。
これは、次の方法で解決されます ( 内A
)。
source "$( dirname "$( which "$0" )" )/lib/B"
ユーザーがどこにいても、どのようにスクリプトを呼び出してもかまいません。これは常に機能します。
与えられた多くの回答を比較し、よりコンパクトなソリューションをいくつか考え出しました。これらは、次のお気に入りの組み合わせから生じるクレイジーなエッジケースをすべて処理しているようです。
script
_ bash script
_ bash -c script
_source script
. script
Linux から実行している場合proc
、現在実行中のスクリプトの完全に解決されたソースを見つけるには、ハンドルを使用するのが最善の解決策のようです (対話型セッションでは、リンクはそれぞれの を指します/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
ファイルシステムを自由に使用できない場合、または他のファイルの完全に解決されたパスを見つけようとしている場合は、おそらく以下のコードが役に立ちます。上記のワンライナーからわずかに変更しただけです。奇妙なディレクトリ/ファイル名で遊んでいる場合は、改行などの代わりに「簡略化された」パスを出力するため、 と の両方ls
で出力を確認するreadlink
と有益です。ls
?
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"
私はこれを持っていると信じています。私はパーティーに遅れましたが、このスレッドに出くわした場合、ここにいることに感謝する人もいると思います. コメントは次のように説明する必要があります。
#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.
## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.
## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).
## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.
## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.
## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.
## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)
## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.
## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.
##===-------------------------------------------------------------------===##
for argv; do :; done # Last parameter on command line, for options parsing.
## Error messages. Use functions so that we can sub in when the error occurs.
recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.
# Probably best not to install as 'pathfull', if you can avoid it.
pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"
## 'test and 'ls' report different status for bad symlinks, so we use this.
if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null; then
errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ]; then
recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
dangling 1>&2; exit 1; fi
fi
## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
fi
## Walk the symlinks back to the origin. Calls itself recursivly as needed.
while [ "$link" ]; do
cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
case "$newlink" in
"$link") dangling 1>&2 && exit 1 ;;
'') printf "$(pwd)/$(basename "$link")\n"; exit 0 ;;
*) link="$newlink" && pathfull "$link" ;;
esac
done
printf "$(pwd)/$(basename "$newlink")\n"
}
## Demo. Install somewhere deep in the filesystem, then symlink somewhere
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".
if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"
# Yay ANSI l33t codes! Fancy.
printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m "
printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n "
printf "Recursive readlink for the authoritative file, symlink after "
printf "symlink.\n\n\n \033[4m$scriptname\033[24m\n\n "
printf " From within an invocation of a script, locate the script's "
printf "own file\n (no matter where it has been linked or "
printf "from where it is being called).\n\n"
else pathfull "$@"
fi
readlinkを組み合わせて名前を正規化し (シンボリックリンクの場合はソースに戻るというボーナス付き) 、ディレクトリ名を抽出するにはdirnameを使用します。
script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"
厄介なことに、実行スクリプトがシンボリックリンクの場合、Linux と macOS の両方で動作する唯一のワンライナーです。
SCRIPT_DIR=$(python -c "import os; print(os.path.dirname(os.path.realpath('${BASH_SOURCE[0]}')))")
または、同様に、python3 pathlib モジュールを使用します。
SCRIPT_DIR=$(python3 -c "from pathlib import Path; print(Path('${BASH_SOURCE[0]}').resolve().parent)")
Linux および macOS でテストされ、この要旨の他のソリューションと比較されました: https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371
/tmp/a/b/c $ . ./test.sh
/tmp/a/b/c
/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c
/tmp/a/b/c $ ./test.sh
/tmp/a/b/c
/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c
/tmp/a/b/c $ cd
~ $ . /tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ . ../../tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ /tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ ../../tmp/a/b/c/test.sh
/tmp/a/b/c
#!/usr/bin/env bash
# snagged from: https://stackoverflow.com/a/51264222/26510
function toAbsPath {
local target
target="$1"
if [ "$target" == "." ]; then
echo "$(pwd)"
elif [ "$target" == ".." ]; then
echo "$(dirname "$(pwd)")"
else
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
fi
}
function getScriptDir(){
local SOURCED
local RESULT
(return 0 2>/dev/null) && SOURCED=1 || SOURCED=0
if [ "$SOURCED" == "1" ]
then
RESULT=$(dirname "$1")
else
RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
fi
toAbsPath "$RESULT"
}
SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"
Most answers either don't handle files which are symlinked via a relative path, aren't one-liners or don't handle BSD (Mac). A solution which does all three is:
HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd)
First, cd to bash's conception of the script's directory. Then readlink the file to see if it is a symlink (relative or otherwise), and if so, cd to that directory. If not, cd to the current directory (necessary to keep things a one-liner). Then echo the current directory via pwd
.
You could add --
to the arguments of cd and readlink to avoid issues of directories named like options, but I don't bother for most purposes.
You can see the full explanation with illustrations here:
https://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html
このソリューションは Bash にのみ適用されます。${BASH_SOURCE[0]}
関数内からパスを見つけようとすると、一般的に提供される回答は機能しないことに注意してください。
ファイルがソースであるか、スクリプトとして実行されているかに関係なく、この行は常に機能することがわかりました。
dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
シンボリックリンクをたどりたい場合はreadlink
、上記のパスを再帰的または非再帰的に使用します。
これを試して、他の提案されたソリューションと比較するためのスクリプトを次に示します。source test1/test2/test_script.sh
またはとして呼び出しbash test1/test2/test_script.sh
ます。
#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"
function test_within_func_inside {
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}
echo "Testing within function inside"
test_within_func_inside
echo "Testing within function outside"
test_within_func_outside
#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}
ワンライナーが機能する理由は、BASH_SOURCE
環境変数とそれに関連付けられFUNCNAME
た .
BASH_SOURCE
FUNCNAME 配列変数内の対応するシェル関数名が定義されているソース ファイル名をメンバーとする配列変数。シェル関数 ${FUNCNAME[$i]} はファイル ${BASH_SOURCE[$i]} で定義され、${BASH_SOURCE[$i+1]} から呼び出されます。
機能名
現在実行コール スタックにあるすべてのシェル関数の名前を含む配列変数。インデックス 0 の要素は、現在実行中のシェル関数の名前です。一番下の要素 (インデックスが最も高い要素) は "main" です。この変数は、シェル関数が実行されている場合にのみ存在します。FUNCNAME への代入は効果がなく、エラー ステータスが返されます。FUNCNAME が設定されていない場合、後でリセットされても、その特別なプロパティは失われます。
この変数は、BASH_LINENO および BASH_SOURCE で使用できます。FUNCNAME の各要素には、BASH_LINENO と BASH_SOURCE に対応する要素があり、コール スタックを記述します。たとえば、${FUNCNAME[$i]} は、ファイル ${BASH_SOURCE[$i+1]} の行番号 ${BASH_LINENO[$i]} から呼び出されました。呼び出し元のビルトインは、この情報を使用して現在の呼び出しスタックを表示します。
[出典: Bash マニュアル]
私は3つの異なる実行で以下を試しました。
echo $(realpath $_)
. application # /correct/path/to/dir or /path/to/temporary_dir
bash application # /path/to/bash
/PATH/TO/application # /correct/path/to/dir
echo $(realpath $(dirname $0))
. application # failed with `realpath: missing operand`
bash application # /correct/path/to/dir
/PATH/TO/application # /correct/path/to/dir
echo $(realpath $BASH_SOURCE)
$BASH_SOURCE
と基本的に同じ${BASH_SOURCE[0]}
です。
. application # /correct/path/to/dir
bash application # /correct/path/to/dir
/PATH/TO/application # /correct/path/to/dir
$(realpath $BASH_SOURCE)
信頼できるように見えるだけです。
この方法の利点の 1 つは、Bash 自体の外部に何も関与せず、サブシェルも fork しないことです。
最初に、パターン置換を使用して、で始まらないもの/
(つまり、相対パス) をに置き換え$PWD/
ます。の最初の文字に一致$0
させるために置換を使用するため、それを (置換で) 追加する必要もあります${0:0:1}
。
これで、スクリプトへのフル パスが得られました。/
最後とそれに続くもの (つまり、スクリプト名)を削除することで、ディレクトリを取得できます。そのディレクトリはcd
、スクリプトに関連する他のパスで、またはプレフィックスとして使用できます。
#!/bin/bash
BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}
cd "$DIR"
スクリプトが実行されるのではなくソースになる可能性がある場合は、もちろん、次のように置き換えることができ$0
ます${BASH_SOURCE[0]}
。
BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}
これは、実行可能なスクリプトでも機能します。より長いですが、より多価です。
私は通常、次のことを行います。
LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
これが純粋なBashソリューションです
$ cat a.sh
BASENAME=${BASH_SOURCE/*\/}
DIRNAME=${BASH_SOURCE%$BASENAME}.
echo $DIRNAME
$ a.sh
/usr/local/bin/.
$ ./a.sh
./.
$ . a.sh
/usr/local/bin/.
$ /usr/local/bin/a.sh
/usr/local/bin/.
重要な部分は、問題の範囲を縮小していることです。パスを介したスクリプトの間接的な実行を禁止しています (のように/bin/sh [script path relative to path component]
)。
$0
これは、現在のフォルダーに関連するファイルに解決されない相対パスになるため、検出できます。このメカニズムを使用した直接実行は、スクリプトがパス上で見つかった場合を含め、#!
常に絶対値になると思います。$0
また、パス名とシンボリック リンクのチェーンに沿ったすべてのパス名には、適切な文字のサブセットのみが含まれている必要があります。特に\n
、、、または. これは、解析ロジックに必要です。>
*
?
私が踏み込まないいくつかの暗黙の期待があり (この回答を見てください)、意図的な妨害行為を処理しようとはしません$0
(したがって、セキュリティへの影響を考慮してください)。Bourne のような/bin/sh
.
#!/bin/sh
(
path="${0}"
while test -n "${path}"; do
# Make sure we have at least one slash and no leading dash.
expr "${path}" : / > /dev/null || path="./${path}"
# Filter out bad characters in the path name.
expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
# Catch embedded new-lines and non-existing (or path-relative) files.
# $0 should always be absolute when scripts are invoked through "#!".
test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
# Change to the folder containing the file to resolve relative links.
folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
cd "${folder}"
# If the last path was not a link then we are in the target folder.
test -n "${path}" || pwd
done
)
私は通常、ほとんどの場合に機能するスクリプトの先頭に次を含めます。
[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');
pwd
最初の行は、現在のパスから実行された場合は の値に基づいてソースを割り当て、他の場所から呼び出された場合はdirnameを割り当てます。
2 行目では、パスがシンボリック リンクであるかどうかを確認し、シンボリック リンクである場合は、SOURCE_DIR をリンク自体の場所に更新します。
おそらくもっと良い解決策があるでしょうが、これは私が自分で思いついた最もクリーンなものです。
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`
一番下の変なディレクトリ名のテストを見てください。
作業ディレクトリを Bash スクリプトが配置されているディレクトリに変更するには、次のシンプルでテスト済みで検証済みのshellcheckソリューションを試す必要があります。
#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2
テスト:
$ ls
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko 46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS
フォーク (サブシェル以外) はなく、次のように改行を含むような「エイリアン」パス名形式を処理できます。
IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
function getScriptAbsoluteDir { # fold>>
# @description used to get the script path
# @param $1 the script $0 parameter
local script_invoke_path="$1"
local cwd=`pwd`
# absolute path ? if so, the first character is a /
if test "x${script_invoke_path:0:1}" = 'x/'
then
RESULT=`dirname "$script_invoke_path"`
else
RESULT=`dirname "$cwd/$script_invoke_path"`
fi
} # <<fold
スクリプトがそのディレクトリで実行されていることを確認したいと思います。それで
cd $(dirname $(which $0) )
この後、実行している場所を本当に知りたい場合は、以下のコマンドを実行します。
DIR=$(/usr/bin/pwd)
現在のスクリプト ディレクトリへのパスを要求する、100% 移植可能で信頼できる方法はありません。特に、 Cygwin、MinGW、MSYS 、 Linux などの異なるバックエンド間では、この問題は Bash では適切かつ完全に解決されていませんでした。
たとえば、source
コマンドの後にパスを要求して、別の Bash スクリプトをネストしてインクルードし、同じsource
コマンドを使用して別の Bash スクリプトをインクルードする場合、これを解決できませんでした。
コマンドの場合、コマンドを次のようなものsource
に置き換えることをお勧めします。source
function include()
{
if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
local include_file_path=...
else
local dir_path=... request the directory from the "$1" argument using one of answered here methods...
local include_file_path=...
fi
... push $CURRENT_SCRIPT_DIR in to stack ...
export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
source "$include_file_path"
... pop $CURRENT_SCRIPT_DIR from stack ...
}
これからは、 is の使用はスクリプト内のinclude(...)
previous に基づいていCURRENT_SCRIPT_DIR
ます。
source
これは、すべてのコマンドをコマンドで置き換えることができる場合にのみ機能しますinclude
。できないなら仕方がない。少なくとも、Bash インタープリターの開発者が、現在実行中のスクリプト ディレクトリ パスを要求する明示的なコマンドを作成するまでは。
これに最も近い私自身の実装:
https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_tacklelib
https://github.com/andry81/tacklelib/tree/trunk/bash/タックルライブラリ/bash_tacklelib
(関数を検索tkl_include
)
複雑にしないでおく。
#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir