从内部获取Bash脚本的源代码目录

如何获取Bash脚本所在目录的path, 该脚本中?

例如,假设我想使用Bash脚本作为另一个应用程序的启动器。 我想将工作目录更改为Bash脚本所在的目录,所以我可以对该目录中的文件进行操作,如下所示:

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

是一个有用的单行程序,它会给你脚本的完整目录名称,无论它从哪里被调用。

只要用于查找脚本的path的最后一个组件不是符号链接(目录链接正常),它就会工作。 如果您还想解决脚本本身的任何链接,则需要一个多线解决scheme:

 SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink DIR="$( cd -P "$( dirname "$SOURCE" )" && 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" )" && pwd )" 

最后一个可以与别名, sourcebash -c ,符号链接等组合使用。

请注意:如果您在运行此代码段之前切换到其他目录,结果可能不正确! 另外,请注意$CDPATH陷阱 。

要理解它是如何工作的,请尝试运行这个更详细的forms:

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

使用dirname

 #!/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命令是最基本的,只需parsing从$ 0(脚本名称)variables到文件名的path即可:

 dirname "$0" 

但是,正如马特·b指出的那样,根据脚本的调用方式,返回的path是不同的。 pwd不会执行这个工作,因为它只告诉你当前目录是什么,而不是脚本所在的目录。另外,如果执行到脚本的符号链接,你将得到一个(可能是相对的)path到链接所在的位置,而不是实际的脚本。

还有一些人提到了readlink命令,但是最简单的,你可以使用:

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

readlink会将脚本pathparsing为文件系统根目录的绝对path。 因此,包含单点或双点,波浪线和/或符号链接的path将被parsing为完整path。

这是一个脚本,演示这些, 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))" 

在我的主目录中运行这个脚本,使用相对path:

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

再次,但使用脚本的完整path:

 >>>$ /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 ”又名. (点)操作符。
  • 当arg $ 0从调用者修改。
  • “。/脚本”
  • “/全/path/到/脚本”
  • “/some/path/../../another/path/script”
  • “./some/folder/script”

或者,如果bash脚本本身是一个相对的符号链接,您可以按照它来返回链接脚本的完整path:

 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都以完整path给出。
只要确保在脚本的开头find了这个。

此评论和代码Copyleft,GPL2.0或更高版本的可选许可证或CC-SA 3.0(CreativeCommons Share Alike)或更高版本。 (c)2008.保留所有权利。 没有任何forms的保证。 你被警告了。
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

简短的回答:

 `dirname $0` 

或( 最好 ):

 $(dirname "$0") 

您可以使用$ BASH_SOURCE

 #!/bin/bash scriptdir=`dirname "$BASH_SOURCE"` 

请注意,您需要使用#!/ bin / bash而不是#!/ bin / sh,因为它是一个bash扩展名

这应该做到这一点:

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

适用于path中的符号链接和空格。 请参阅手册页以获取dirnamereadlink

编辑:

从评论的轨道,它似乎不适用于Mac OS。 我不知道这是为什么。 有什么build议么?

pwd可以用来查找当前工作目录,而dirname用来查找特定文件的目录(运行的命令是$0 ,所以dirname $0应该给你当前脚本的目录)。

但是, dirname正好给出了文件名的目录部分,这个可能性大于当前的工作目录。 如果您的脚本由于某种原因需要更改目录,则dirname的输出将变得毫无意义。

我build议如下:

 #!/bin/bash reldir=`dirname $0` cd $reldir directory=`pwd` echo "Directory is $directory" 

这样,你会得到一个绝对的,而不是相对的目录。

由于脚本将在一个单独的bash实例中运行,所以之后不需要恢复工作目录,但是如果您因为某种原因想要更改脚本,可以轻松地将pwd的值赋给一个variables你改变目录,以备将来使用。

虽然只是

 cd `dirname $0` 

解决了这个问题中的具体情况,我觉得有一个绝对的path,一般更有用。

我认为这不像其他人那样容易。 pwd不起作用,因为当前目录不一定是脚本的目录。 $ 0并不总是有信息。 考虑以下三种调用脚本的方法。

 ./script /usr/bin/script script 

在第一种和第三种方式中,$ 0没有完整的path信息。 在第二和第三,pwd不工作。 以第三种方式获取目录的唯一方法是运行path并find正确匹配的文件。 基本上,代码将不得不重做什么操作系统。

一种方法来做你所要求的只是硬编码/ usr / share目录中的数据,并通过完整path引用它。 无论如何,数据并不在/ usr / bin目录中,所以这可能是要做的事情。

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

这会在Mac OS X 10.6.6上获得当前的工作目录:

 DIR=$(cd "$(dirname "$0")"; pwd) 

这是Linux特定的,但你可以使用:

 SELF=$(readlink /proc/$$/fd/255) 
 $(dirname $(readlink -f $BASH_SOURCE)) 

这是符合POSIX标准的一行:

 SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"` # test echo $SCRIPT_PATH 

我尝试了其中的每一个,都没有工作。 一个非常接近,但有一个小小的错误,打破了它; 他们忘了用引号包起来的path。

也有很多人认为你正在从一个shell运行脚本,所以当你打开一个新的脚本,它默认你的家。

试试这个目录的大小:

/ var / No one / Thought /关于空间存在/在一个目录/名称/这是你的file.text

无论您如何或在哪里运行它,这都是正确的。

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

所以为了使它实际上有用这里是如何改变到正在运行的脚本的目录:

 cd "`dirname "$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 

解答e-satis和3bcdnlklvc04a的一个小的修改在他们的答复中指出了

 SCRIPT_DIR='' pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR="$PWD" popd > /dev/null } 

这应该仍然适用于他们列出的所有情况。

编辑:防止失败pushd后,popup感谢konsolebox

以下是简单,正确的方法:

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

说明:

  • ${BASH_SOURCE[0]} – 脚本的完整path。 即使脚本正在源代码中,这个值也是正确的,例如source <(echo 'echo $0') ${BASH_SOURCE[0]} source <(echo 'echo $0')打印bash ,而用${BASH_SOURCE[0]}replace它将打印脚本的完整path。 (当然,这假定你可以依赖Bash。)

  • readlink -f – recursionparsing指定path中的任何符号链接。 这是一个GNU扩展,并不适用于(例如)BSD系统。 如果您正在运行Mac,则可以使用Homebrew安装GNU coreutils并用greadlink -f代替它。

  • 当然dirname获取path的父目录。

 #!/bin/sh PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do PRG=`readlink "$PRG"` done scriptdir=`dirname "$PRG"` 

$ _值得一提,作为$ 0的替代。 如果您使用bash运行脚本,可以将接受的答案缩短为:

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

请注意,这必须是脚本中的第一条语句。

嗯,如果在pathbasename&dirname只是不会削减它,走路是艰难的(如果父母没有导出PATH!)。 但是,shell必须有一个打开的句柄,在bash中,句柄是#255。

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

为我工作。

我比较了许多答案,并提出了一些更紧凑的解决scheme。 这些似乎处理所有你最喜欢的组合所产生的疯狂边缘情况:

  • 绝对path或相对path
  • 文件和目录软链接
  • 作为scriptbash scriptbash -c scriptsource script或者. script . script
  • 目录和/或文件名中的空格,制表符,换行符,unicode等
  • 文件名以连字符开头

如果您是从Linux运行的,似乎使用proc句柄是find当前正在运行的脚本完全parsing源的最佳解决scheme(在交互式会话中,链接指向相应的/dev/pts/X ):

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

这有一点丑陋,但修复紧凑,易于理解。 我们不仅仅使用bash原语,但是我很好,因为readlink简化了这个任务。 echo Xecho X添加到variablesstring的末尾,以便文件名中的任何尾随空格不会被占用,并且行末尾的参数replace${VAR%X}将被删除。 因为readlink增加了一个自己的换行符(如果不是我们以前的readlink ,通常会在命令replace中被使用),我们也必须去掉这个换行符。 这是使用$''引用scheme最容易完成的,它使我们可以使用转义序列(如\n来表示换行符(这也是您可以轻松制作不透明的命名目录和文件的方式)。

以上内容应该涵盖了在Linux上查找当前正在运行的脚本的需求,但是如果您没有proc文件系统,或者您正在尝试查找某个其他文件的完全parsingpath,会发现下面的代码有用。 这只是上面一行的一个小小的修改而已。 如果你正在玩奇怪的目录/文件名,用lsreadlink检查输出是提供信息的,因为ls会输出“简化”path,用? 像换行符的东西。

 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" 

尝试使用:

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

所以…我相信我有这个。 晚会到了,但是我想有些人会欣赏它在这里是他们遇到这个线程。 意见应该解释。

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

For systems having GNU coreutils readlink (eg. linux):

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

No need to use BASH_SOURCE when $0 contains the script filename.

Try the following cross-compatible solution:

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

as realpath or readlink commands are not always available (depending on the operating system) and ${BASH_SOURCE[0]} is available only in bash shell.

Alternatively you can try the following function in bash:

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

This function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix).

有关:

  • 如何将当前工作目录设置为脚本的目录?
  • Bash script absolute path with OSX
  • 一个bash脚本可靠的方式来获得自己的完整path?

This works in bash-3.2:

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

Here's an example of its usage:

Say you have a ~/bin directory, which is in your $PATH . You have script A inside this directory. It source s script ~/bin/lib/B . You know where the included script is relative to the original one (the subdirectory lib ), but not where it is relative to the user's current directory.

This is solved by the following (inside A ):

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

It doesn't matter where the user is or how he calls the script, this will always work.

The best compact solution in my view would be:

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

There is no reliance on anything other than Bash. The use of dirname , readlink and basename will eventually lead to compatibility issues, so they are best avoided if at all possible.

This is the only way I've found to tell reliably:

 SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd)) 

This worked for me when the other answers here did not:

 thisScriptPath=`realpath $0` thisDirPath=`dirname $thisScriptPath` echo $thisDirPath