如何从脚本本身中获取Bash脚本所在的目录?

如何获取Bash脚本所在目录的路径,里面该脚本?

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

$ ./application
2308526 次浏览

使用dirname "$0"

#!/usr/bin/env 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.shThe script you are running has basename test2.sh, dirname .The present working directory is /home/matt[matt@server1 ~]$ cd /tmp[matt@server1 tmp]$ ~/test2.shThe script you are running has basename test2.sh, dirname /home/mattThe present working directory is /tmp

您可以使用$BASH_SOURCE

#!/usr/bin/env bash
scriptdir="$( dirname -- "$BASH_SOURCE"; )";

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

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

./script
/usr/bin/script
script

在第一种和第三种方式中,$0没有完整的路径信息。在第二种和第三种情况下,pwd不起作用。以第三种方式获取目录的唯一方法是运行路径并找到具有正确匹配的文件。基本上,代码必须重做操作系统所做的事情。

一种方法是将/usr/share目录中的数据硬编码,并通过其完整路径引用它。数据无论如何都不应该在/usr/bin目录中,所以这可能是要做的事情。

#!/bin/shPRG="$0"
# need this for relative symlinkswhile [ -h "$PRG" ] ; doPRG=`readlink "$PRG"`done
scriptdir=`dirname "$PRG"`

pwd可用于查找当前工作目录,dirname用于查找特定文件的目录(运行的命令是$0,因此dirname $0应该为您提供当前脚本的目录)。

但是,dirname准确地给出了文件名的目录部分,这很可能是相对于当前工作目录的。如果您的脚本出于某种原因需要更改目录,那么dirname的输出将变得毫无意义。

我建议如下:

#!/usr/bin/env bash
reldir="$( dirname -- "$0"; )";cd "$reldir";directory="$( pwd; )";
echo "Directory is ${directory}";

这样,您将获得绝对目录,而不是相对目录。

由于脚本将在单独的Bash实例中运行,因此之后不需要恢复工作目录,但是如果您确实出于某种原因想要更改回脚本,您可以在更改目录之前轻松地将pwd的值分配给变量,以供将来使用。

虽然只是

cd "$( dirname -- "$0"; )";

解决了问题中的具体情况,我发现一般来说有绝对路径更有用。

这Linux具体的,但您可以使用:

SELF=$(readlink /proc/$$/fd/255)
pushd . > '/dev/null';SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";
while [ -h "$SCRIPT_PATH" ];docd "$( dirname -- "$SCRIPT_PATH"; )";SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";done
cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';SCRIPT_PATH="$( pwd; )";popd  > '/dev/null';

它适用于所有版本,包括

  • 当通过多深度软链接调用时,
  • 当文件它
  • 当脚本通过命令“source”又名.(点)运算符调用时。
  • 当arg$0从调用者修改时。
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

或者,如果Bash脚本本身是相对符号链接,您可以想要跟随它并返回链接到脚本的完整路径:

pushd . > '/dev/null';SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";
while [ -h "$SCRIPT_PATH" ];docd "$( dirname -- "$SCRIPT_PATH"; )";SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";done
cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';SCRIPT_PATH="$( pwd; )";popd  > '/dev/null';

SCRIPT_PATH以全路径给出,无论它是如何调用的。

只要确保您在脚本的开头找到它。

在Bash 3.2中:

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

如果您的$PATH中有~/bin目录,则此目录中有A。它是脚本~/bin/lib/B的来源。您知道包含的脚本相对于原始脚本的位置,在lib子目录中,但不知道它相对于用户当前目录的位置。

这是通过以下方式解决的(在A中):

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

用户在哪里或他/她如何调用脚本并不重要。这总是有效的。

#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

是一个有用的一行程序,它将为您提供脚本的完整目录名称,无论它从何处调用。

只要用于查找脚本的路径的最后一个组件不是符号链接(目录链接可以),它就可以工作。如果您还想解析到脚本本身的任何链接,您需要多行解决方案:

#!/usr/bin/env bash
SOURCE=${BASH_SOURCE[0]}while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlinkDIR=$( 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 locateddoneDIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )

最后一个将使用别名、sourcebash -c、符号链接等的任何组合。

注意:如果您在运行此代码段之前cd到不同的目录,结果可能不正确!

此外,如果用户巧妙地覆盖cd以将输出重定向到stderr(包括转义序列,例如在Mac上调用update_terminal_cwd >&2时),请注意#0陷阱和stderr输出的副作用。在cd命令末尾添加>/dev/null 2>&1将照顾这两种可能性。

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

#!/usr/bin/env bash
SOURCE=${BASH_SOURCE[0]}while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlinkTARGET=$(readlink "$SOURCE")if [[ $TARGET == /* ]]; thenecho "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"SOURCE=$TARGETelseDIR=$( 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 locatedfidoneecho "SOURCE is '$SOURCE'"RDIR=$( dirname "$SOURCE" )DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )if [ "$DIR" != "$RDIR" ]; thenecho "DIR '$RDIR' resolves to '$DIR'"fiecho "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";`"

或者(优选):

"$( dirname -- "$0"; )"

我通常做:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")source $LIBDIR/lib.sh

嗯,如果在路径中,#0#1不会切断它,并且行走路径很难(如果父节点没有导出PATH怎么办?!)。

但是,shell必须有一个打开的脚本句柄,在Bash中句柄是#255。

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

为我工作。

我想确保脚本在其目录中运行。所以

cd $(dirname $(which $0) )

在此之后,如果您真的想知道您正在运行的位置,请运行下面的命令。

DIR=$(/usr/bin/pwd)

这是我找到的唯一可靠的方法:

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

#0命令是最基本的,只需解析$0(脚本名称)变量的文件名的路径:

dirname -- "$0";

但是,正如matb所指出的,返回的路径取决于脚本的调用方式。pwd不做这项工作,因为它只告诉你当前目录是什么,而不是脚本驻留的目录。此外,如果执行脚本的符号链接,你将获得链接驻留的(可能是相对的)路径,而不是实际的脚本。

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

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

readlink将脚本路径解析为从文件系统根目录开始的绝对路径。因此,任何包含单点或双点、波浪号和/或符号链接的路径都将解析为完整路径。

这里有一个脚本演示了其中的每一个,whatdir.sh

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

在我的主目录中运行此脚本,使用相对路径:

>>>$ ./whatdir.shpwd: /Users/phatblat$0: ./whatdir.shbasename: whatdir.shdirname: .dirname/readlink: /Users/phatblat

同样,但使用脚本的完整路径:

>>>$ /Users/phatblat/whatdir.shpwd: /Users/phatblat$0: /Users/phatblat/whatdir.shbasename: whatdir.shdirname: /Users/phatblatdirname/readlink: /Users/phatblat

现在更改目录:

>>>$ cd /tmp>>>$ ~/whatdir.shpwd: /tmp$0: /Users/phatblat/whatdir.shbasename: whatdir.shdirname: /Users/phatblatdirname/readlink: /Users/phatblat

最后使用符号链接执行脚本:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh>>>$ ./whatdirlink.shpwd: /tmp$0: ./whatdirlink.shbasename: whatdirlink.shdirname: .dirname/readlink: /Users/phatblat

然而,当脚本在bash中来源(而不是执行)时,有一种情况不起作用:

>>>$ cd /tmp>>>$ . ~/whatdir.shpwd: /tmp$0: bashbasename: bashdirname: .dirname/readlink: /tmp
function getScriptAbsoluteDir { # fold>># @description used to get the script path# @param $1 the script $0 parameterlocal 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/'thenRESULT=`dirname "$script_invoke_path"`elseRESULT=`dirname "$cwd/$script_invoke_path"`fi} # <<fold

这是对e-satis和3bcdnlvelvc04a在他们的答案中指出的解决方案的轻微修改:

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

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

这将防止pushd失败后的popd。感谢konsolebox。

我尝试了所有这些方法,但没有一个奏效。其中一个非常接近,但它有一个小bug严重破坏了它;他们忘记用引号包装路径。

还有很多人认为您正在从shell运行脚本,因此他们忘记了当您打开新脚本时,它默认为您的家。

试试这个目录的大小:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

无论你如何或在哪里运行它,它都是正确的:

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

因此,为了使其真正有用,以下是如何更改到正在运行的脚本的目录:

cd "`dirname "$0"`"

这些其他答案都不适用于osxFinder启动的Bash脚本。我最终使用了:

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

它不漂亮,但它完成了工作。

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

使用读链接的组合来规范名称(如果它是符号链接,则可以将其追溯到其源)和迪尔名称的组合来提取目录名:

script="`readlink -f "${BASH_SOURCE[0]}"`"dir="`dirname "$script"`"

这获取Mac OS X v10.6.6(Snow Leopard)上的当前工作目录:

DIR=$(cd "$(dirname "$0")"; pwd)
$(dirname "$(readlink -f "$BASH_SOURCE")")

#0作为$0的替代方案值得一提。如果您正在从Bash运行脚本,接受的答案可以缩短为:

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

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

尝试使用:

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

我会用这样的东西:

# Retrieve the full pathname of the called scriptscriptPath=$(which $0)
# Check whether the path is a link or notif [ -L $scriptPath ]; then
# It is a link then retrieve the target path and get the directory namesourceDir=$(dirname $(readlink -f $scriptPath))
else
# Otherwise just get the directory name of the script pathsourceDir=$(dirname $scriptPath)
fi

这是一个纯Bash解决方案

$ cat a.shBASENAME=${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/.

这是一个符合POSIX的单行代码:

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

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

  • 绝对路径或相对路径
  • 文件和目录软链接
  • 调用为scriptbash scriptbash -c scriptsource script. script
  • 目录和/或文件名中的空格、制表符、换行符、Unicode等
  • 以连字符开头的文件名

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

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

这有一点丑陋,但修复是紧凑且易于理解的。我们不仅仅使用bash原语,但我可以接受,因为#0大大简化了任务。echo X在变量字符串的末尾添加了X,这样文件名中的任何尾随空格都不会被吃掉,并且行尾的参数替换${VAR%X}摆脱了X。因为readlink添加了自己的换行符(如果不是我们之前的技巧,它通常会在命令替换中被吃掉),我们也必须摆脱它。这是使用$''引用方案最容易完成的,它允许我们使用\n等转义序列来表示换行符(这也是您可以轻松创建不正确命名的目录和文件的方式)。

上面应该涵盖了你在Linux上定位当前运行脚本的需求,但是如果你没有proc文件系统可供使用,或者如果你试图定位其他文件的完全解析路径,那么也许你会发现下面的代码很有用。这只是对上面一行代码的轻微修改。如果你在玩奇怪的目录/文件名,检查lsreadlink的输出会提供有用的信息,因为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"

我通常在我的脚本顶部包含以下内容,这在大多数情况下都有效:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

如果从当前路径运行,第一行根据pwd的值分配源,如果从其他地方调用,则根据迪尔名称的值分配源。

第二行检查路径以查看它是否是符号链接,如果是,则更新SOURCE_DIR链接本身的位置。

可能有更好的解决方案,但这是我自己想出的最干净的。

试试这样的东西:

function get_realpath() {
if [[ -f "$1" ]]then# The file *must* existif cd "$(echo "${1%/*}")" &>/dev/nullthen# The file *may* not be local.# The exception is ./file.ext# tTry 'cd .; cd -;' *works!*local tmppwd="$PWD"cd - &>/dev/nullelse# file *must* be locallocal tmppwd="$PWD"fielse# The file *cannot* existreturn 1 # Failurefi
# Reassemble realpathecho "$tmppwd"/"${1##*/}"return 0 # Success
}
function get_dirname(){
local realpath="$(get_realpath "$1")"if (( $? )) # True when non-zero.thenreturn $? # Failurefiecho "${realpath%/*}"return 0 # Success
}
# Then from the top level:get_dirname './script.sh'
# Or within a script:get_dirname "$0"
# Can even test the outcome!if (( $? )) # True when non-zero.thenexit 1 # Failurefi

这些功能和相关工具是我们产品的一部分,已经免费提供给社区,可以在GitHub上作为里程路径库找到。它简单、干净、有据可查(非常适合学习)、纯Bash且没有依赖项。也适合跨平台使用。对于上面的例子,在一个脚本中,你可以简单地:

source '/path/to/realpath-lib'
get_dirname "$0"
if (( $? )) # True when non-zero.thenexit 1 # Failurefi

在我看来,最好的紧凑解决方案是:

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

不依赖Bash以外的任何东西。#0#1#2的使用最终会导致兼容性问题,所以如果可能的话,最好避免它们。

这是我对外壳脚本:检查目录名并转换为小写的回答的摘录,其中我不仅演示了如何使用非常基本的POSIX指定的实用程序解决这个问题,我还解决了如何非常简单地将函数的结果存储在返回的变量中…

……嗯,正如你所看到的,使用一些帮助,我想到了一个非常简单且非常强大的解决方案:

我可以向函数传递某种信使变量,并根据需要取消对结果函数参数$1名称的任何显式使用,并且在函数例程完成后,我使用eval和反斜杠引用技巧为我的Messenger变量分配我想要的值,而无需知道它的名称。

在充分披露,…(我找到了这个信使变量部分),在Rich的诡计,我也摘录了他页面的相关部分,下面是我自己的答案摘录。

节选:

虽然还没有严格的POSIX,Realpath是自2012年以来的GNU核心应用程序.完全披露:在我在info coreutils TOC中注意到它并立即想到[链接的]问题之前从未听说过它,但是使用以下演示的函数应该可靠,(很快POSIXLY?),而且,我希望,有效为调用者提供一个绝对来源的$0

% _abs_0() {> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";> }% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"/no/more/dots/in/your/path2.sh

值得强调的是,这个解决方案使用POSIX参数扩展首先检查路径是否真的需要扩展和解析,然后再尝试这样做。这应该是通过Messenger变量返回一个绝对来源的#0(值得注意的例外是它将保留#1)作为有效,因为我可以想象它可以完成是否路径已经是绝对的。

小编辑:在文档中找到#0之前,我至少减少了我的版本(下面的版本)不依赖于时间字段(就像在第一个#1命令中所做的那样),但是,公平的警告,在测试了一些之后,我不太相信#1在其命令路径扩展能力方面是完全可靠的

另一方面,你可以这样做:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"
eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

Rich的诡计

从shell函数返回字符串

从上面命令替换的陷阱可以看出,标准输出不是shell函数将字符串返回给调用者的好途径,除非输出的格式是尾随换行符无关紧要的。当然,对于旨在处理任意字符串的函数来说,这种做法是不可接受的。那么,能做些什么呢?

试试这个:

func () {body hereeval "$1=\${foo}"}

当然,${foo}可以用任何类型的替换来替换。这里的关键技巧是val行和转义的使用。当主命令解析器构造了val的参数时,“$1”会展开。但是“${foo}”在这个阶段没有展开,因为“$”已经被引用了。相反,当val评估它的参数时,它会展开。如果不清楚为什么这很重要,请考虑以下情况会有多糟糕:

foo='hello ; rm -rf /'dest=bareval "$dest=$foo"

当然,以下版本是完全安全的:

foo='hello ; rm -rf /'dest=bareval "$dest=\$foo"

请注意,在原始示例中,“$1”用于允许调用者将目标变量名称作为函数的参数传递。如果您的函数需要使用Shift命令,例如将剩余参数作为“$@”处理,那么将“$1”的值保存在函数开头的临时变量中可能会很有用。

尝试以下交叉兼容解决方案:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

因为realpathreadlink等命令可能不可用(取决于操作系统)。

注意:在Bash中,建议使用${BASH_SOURCE[0]}而不是$0,否则在获取文件时路径可能会中断(source/.)。

或者,您可以在Bash中尝试以下函数:

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

此函数接受一个参数。如果参数已经有绝对路径,请按原样打印,否则打印$PWD变量+文件名参数(没有./前缀)。

相关:

我相信我有这个。我迟到了,但我想如果他们遇到这个线程,有些人会很感激它在这里。评论应该解释:

#!/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;  thenerrnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   thenrecurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; thendangling 1>&2; exit 1; fifi
## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; thenprintf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0fi
## Walk the symlinks back to the origin. Calls itself recursivly as needed.
while [ "$link" ]; docd "$(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"                           ;;esacdoneprintf "$(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
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`

对于具有GNU coreutilsreadlink的系统(例如,Linux):

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

$0包含脚本文件名时,无需使用BASH_SOURCE

没有分叉(除了subshell),并且可以处理“外来”路径名表单,例如一些人声称的带有换行符的表单:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")

此解决方案仅适用于Bash。请注意,如果您尝试从函数中查找路径,通常提供的答案${BASH_SOURCE[0]}将不起作用。

我发现这一行始终有效,无论文件是作为脚本获取还是作为脚本运行。

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

如果您想遵循符号链接,请在上面的路径上使用#0,递归或非递归。

这里有一个脚本来尝试它并将其与其他建议的解决方案进行比较。将其调用为source test1/test2/test_script.shbash test1/test2/test_script.sh

## Location: test1/test2/test_script.sh#echo $0echo $_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数组变量中相应的shell函数名称。shell函数${FUNCNAME[$i]}在文件${BASH_SOURCE[$i]}中定义,并从${BASH_SOURCE[$i+1]}调用。

FUNCNAME

一个数组变量,包含当前执行调用堆栈中所有shell函数的名称。索引为0的元素是任何当前执行的shell函数的名称。最底部的元素(索引最高的元素)是“main”。此变量仅在shell函数执行时存在。对FUNCNAME的赋值没有任何效果并返回错误状态。如果FUNCNAME未设置,它将失去其特殊属性,即使它随后被重置。

此变量可用于BASH_LINENO和BASH_SOURCE。FUNCNAME的每个元素在BASH_LINENO和BASH_SOURCE中都有相应的元素来描述调用堆栈。例如,${FUNCNAME[$i]}是从文件${BASH_SOURCE[$i+1]}中的行号${BASH_LINENO[$i]}调用的。调用者内置程序使用此信息显示当前调用堆栈。

[来源:Bash手册]

FOLDERNAME=${PWD##*/}

这是我知道的最快的方法。

关键部分是我缩小了问题的范围:我禁止通过路径间接执行脚本(如/bin/sh [script path relative to path component])。

这可以被检测到,因为$0将是一个相对路径,它不会解析为相对于当前文件夹的任何文件。我相信使用#!机制的直接执行总是会导致绝对的$0,包括在路径上找到脚本时。

我还要求路径名和符号链接链上的任何路径名只包含合理的字符子集,特别是不包含\n>*?。这是解析逻辑所必需的。

还有一些隐含的期望我不会深入讨论(看看这个答案),我也不会试图处理对$0的蓄意破坏(所以考虑任何安全影响)。我希望这在几乎任何具有类似Bourne/bin/sh的类Unix系统上都能工作。

#!/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 1path=`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}" || pwddone)

这应该这样做:

DIR="$(dirname "$(realpath "$0")")"

这适用于路径中的符号链接和空格。

请参阅dirnamerealpath的手册页。

请添加有关如何支持MacOS的评论。对不起,我可以验证它。

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

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安装GNUcoreutils并用greadlink -f取代它。

  • 当然,dirname获取路径的父目录。

看看底部带有奇怪目录名称的测试。

要将工作目录更改为Bash脚本所在的目录,您应该尝试以下简单的测试并使用shellcheck解决方案进行验证:

#!/bin/bash --cd "$(dirname "${0}")"/. || exit 2

测试:

$ lsapplication$ 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 -lbtotal 4lrwxrwxrwx 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\ !"#$%&'testdirdrwxr-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

基于这个答案,我建议将SCRIPT_HOME作为任何当前运行的Bash脚本的包含文件夹的澄清版本:

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`echo $SCRIPT_HOME

单行适用于Cygwin,即使脚本已从windowsbash -c <script>调用:

set mydir="$(cygpath "$(dirname "$0")")"

$0不是获取当前脚本路径的可靠方法。例如,这是我的.xprofile

#!/bin/bashecho "$0 $1 $2"echo "${BASH_SOURCE[0]}"# $dir/my_script.sh &

cd /tmp&&~/. xProfile&&source~/. xProfile

/home/puchuu/.xprofile/home/puchuu/.xprofile-bash/home/puchuu/.xprofile

所以请使用BASH_SOURCE代替。

当这里的其他答案没有时,这对我有用:

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

这些是获取脚本信息的简短方法:

文件夹和文件:

    Script: "/tmp/src dir/test.sh"Calling folder: "/tmp/src dir/other"

使用这些命令:

    echo Script-Dir : `dirname "$(realpath $0)"`echo Script-Dir : $( cd ${0%/*} && pwd -P )echo Script-Dir : $(dirname "$(readlink -f "$0")")echoecho Script-Name : `basename "$(realpath $0)"`echo Script-Name : `basename $0`echoecho Script-Dir-Relative : `dirname "$BASH_SOURCE"`echo Script-Dir-Relative : `dirname $0`echoecho Calling-Dir : `pwd`

我得到了这个输出:

     Script-Dir : /tmp/src dirScript-Dir : /tmp/src dirScript-Dir : /tmp/src dir
Script-Name : test.shScript-Name : test.sh
Script-Dir-Relative : ..Script-Dir-Relative : ..
Calling-Dir : /tmp/src dir/other

另见:https://pastebin.com/J8KjxrPF

这就是我在脚本上的工作方式:

pathvar="$( cd "$( dirname $0 )" && pwd )"

这将告诉您正在从哪个目录执行Launcher(当前脚本)。

如果你的Bash脚本是一个符号链接,那么这就是方法:

#!/usr/bin/env bash
dirn="$(dirname "$0")"rl="$(readlink "$0")";exec_dir="$(dirname $(dirname "$rl"))";my_path="$dirn/$exec_dir";X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"

X是包含您的Bash脚本的目录(原始文件,而不是符号链接)。我向上帝发誓这是有效的,这是我知道正确执行此操作的唯一方法。

这是一个容易记住的脚本:

DIR="$( dirname -- "${BASH_SOURCE[0]}"; )";   # Get the directory nameDIR="$( realpath -e -- "$DIR"; )";    # Resolve its full path if need be

读链接46128">选择的答案工作得很好。我发布了我的解决方案,供任何寻找更短替代方案的人使用,这些替代方案仍然涉及采购、执行、完整路径、相对路径和符号链接。最后,这将适用于macOS,因为不能假设GNU的coreutils版本读链接可用。

问题是它没有使用Bash,但在Bash脚本中很容易使用。虽然OP没有对解决方案的语言施加任何限制,但大多数人最好留在Bash世界中。这只是一个替代方案,可能是一个不受欢迎的方案。

默认情况下,PHP在macOS上可用,并安装在许多其他平台上,尽管不一定是默认的。我意识到这是一个缺点,但无论如何,我会把它留给任何来自搜索引擎的人。

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"

下面将脚本的目录路径存储在dir变量中。

(它还尝试支持在Windows中在Cygwin下执行。)

最后,它运行my-sample-app可执行文件,并使用"$@"将所有参数传递给此脚本:

#!/usr/bin/env sh
dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)
if [ -d /proc/cygdrive ]; thencase "$(uname -s)" inCYGWIN*|MINGW32*|MSYS*|MINGW*)# We are under Windows, so translate path to Windows format.dir=$(cygpath -m "$dir");;;esacfi
# Runs the executable which is beside this script"${dir}/my-sample-app" "$@"

没有100%可移植和可靠的方法来请求当前脚本目录的路径。特别是在不同的后端之间,如CygwinMinGWMSYS、Linux等。这个问题在Bash中很久没有得到正确和完全的解决。

例如,如果您想请求source命令之后的路径以嵌套包含另一个Bash脚本,则无法解决此问题,该脚本又使用相同的source命令包含另一个Bash脚本等等。

#0命令的情况下,我建议用这样的东西替换source命令:

function include(){if [[ -n "$CURRENT_SCRIPT_DIR" ]]; thenlocal dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...local include_file_path=...elselocal 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 ...}

从现在开始,include(...)的使用基于脚本中的前一个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/tacklelib/bash_tacklelib

(搜索tkl_include函数)

我通常使用:

dirname $(which $BASH_SOURCE)

这是一个在Bash或zsh下工作的命令,无论是独立执行还是源执行:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \|| this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

它是如何运作的

zsh当前文件扩展名:${(%):-%x}

zsh中的${(%):-%x}扩展到当前执行文件的路径。

回退替换运算符:-

您已经知道${...}在字符串中替换变量。您可能不知道在替换期间可以对变量进行某些操作(在Bashzsh中),例如回退扩展运算符:-

% x=ok% echo "${x}"ok
% echo "${x:-fallback}"ok
% x=% echo "${x:-fallback}"fallback
% y=yvalue% echo "${x:-$y}"yvalue

%x转义码

接下来,我们将介绍提示转义代码,这是一个仅限zsh的功能。在zsh中,%x将扩展到文件的路径,但通常这仅在对提示字符串进行扩展时才会发生。为了在我们的替换中启用这些代码,我们可以在变量名之前添加(%)标志:

% cat apath/test.shfpath=%xecho "${(%)fpath}"
% source apath/test.shapath/test.sh
% cd apath% source test.shtest.sh

一个不太可能的匹配:百分比逃逸和后备

到目前为止,我们的方法有效,但为了避免创建额外的fpath变量,它会更整洁。我们可以使用:-并将%x放在后备字符串中,而不是将%x放在fpath中:

% cat test.shecho "${(%):-%x}"
% source test.shtest.sh

请注意,我们通常会将变量名放在(%):-之间,但我们将其留空。具有空白名称的变量无法声明或设置,因此总是会触发回退。

上一篇:什么是print -P %x

现在我们几乎有了脚本的目录。我们可以使用print -P %x以更少的黑客攻击获得相同的文件路径,但在我们的例子中,我们需要将其作为参数传递给dirname,这将需要启动一个新的子shell的开销:

% cat apath/test.shdirname "$(print -P %x)"  # $(...) runs a command in a new processdirname "${(%):-%x}"
% source apath/test.shapathapath

事实证明,hacky方式既更高效又简洁。

做到这一点的最短和最优雅的方法是:

#!/bin/bashDIRECTORY=$(cd `dirname $0` && pwd)echo $DIRECTORY

这将在所有平台上工作,并且非常干净。

更多细节可以在“bash脚本在哪个目录?”中找到。

我想评论前面的答案(如何从脚本本身中获取Bash脚本的源目录?),但没有足够的声誉来做到这一点。

两年前我在苹果的留档网站上找到了一个解决方案:https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/AdvancedTechniques/AdvancedTechniques.html。后来我坚持使用这个方法。它不能处理软链接,但对我来说效果很好。我把它贴在这里给任何需要它的人,并作为征求意见的请求。

#!/bin/sh
# Get an absolute path for the poem.txt file.POEM="$PWD/../poem.txt"
# Get an absolute path for the script file.SCRIPT="$(which $0)"if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; thenSCRIPT="$PWD/$SCRIPT"fi

如代码所示,获得脚本的绝对路径后,就可以使用#0命令获取目录的路径了。

您可以将脚本名称($0)与#1和/或#2组合起来。它适用于Bash和Shell。

#!/usr/bin/env bash
RELATIVE_PATH="${0}"RELATIVE_DIR_PATH="$(dirname "${0}")"FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"FULL_PATH="$(realpath "${0}")"
echo "RELATIVE_PATH->${RELATIVE_PATH}<-"echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"echo "FULL_PATH->${FULL_PATH}<-"

输出将是这样的:

# RELATIVE_PATH->./bin/startup.sh<-# RELATIVE_DIR_PATH->./bin<-# FULL_DIR_PATH->/opt/my_app/bin<-# FULL_PATH->/opt/my_app/bin/startup.sh<-

0美元是剧本本身的名字

4.4.特殊变量类型

示例:LozanoMatheus/get_script_paths.sh

这是我多年来精心制作的,用作Bash脚本的标头:

## BASE BRAIN - Get where you're from and who you are.MYPID=$$ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..fa="$0" # First Assumptionta= # Temporary Assumptionwa= # Weighed Assumptionwhile true; do[ "${fa:0:1}" = "/" ] && wa=$0 && break[ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && breakta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && breakdoneSW="$wa"SWDIR="$(dirname "$wa")"SWBIN="$(basename "$wa")"unset ta fa wa( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1

此时,您的变量SW、SWDIR和SWBIN包含您需要的内容。

以下代码将返回脚本的当前目录

  • 如果它是来源的,或者不是来源的
  • 如果在当前目录或其他目录中运行,则有效。
  • 如果使用相对目录,则有效。
  • 适用于bash,不确定其他shell。
/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

test.sh

#!/usr/bin/env bash
# snagged from: https://stackoverflow.com/a/51264222/26510function toAbsPath {local targettarget="$1"
if [ "$target" == "." ]; thenecho "$(pwd)"elif [ "$target" == ".." ]; thenecho "$(dirname "$(pwd)")"elseecho "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"fi}
function getScriptDir(){local SOURCEDlocal RESULT(return 0 2>/dev/null) && SOURCED=1 || SOURCED=0
if [ "$SOURCED" == "1" ]thenRESULT=$(dirname "$1")elseRESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"fitoAbsPath "$RESULT"}
SCRIPT_DIR=$(getScriptDir "$0")echo "$SCRIPT_DIR"

最高响应并非在所有情况下都有效…

由于我在一些非常新鲜和不太新鲜的安装ubuntu 16.04(Xenial Xerus)系统上使用包含的“cd”方法的BASH_SOURCE遇到问题,当通过“shmy_script.sh”调用外壳脚本时,我尝试了一些不同的东西,到目前为止似乎运行得很顺利。

这种替代方法使用coreutils包中的外部应用程序“Realpath”和“迪尔名称”。(好吧,没有人喜欢调用辅助进程的开销-但是当看到多行脚本来解析真正的对象时,用单个二进制使用它也不会那么糟糕。)

因此,让我们看一个替代解决方案的示例,用于查询某个文件的真实绝对路径的描述任务:

PATH_TO_SCRIPT=`realpath -s $0`PATH_TO_SCRIPT_DIR=`dirname $PATH_TO_SCRIPT`

但最好你应该使用这个进化版本来支持使用带有空格的路径(甚至可能是其他一些特殊字符):

PATH_TO_SCRIPT=`realpath -s "$0"`PATH_TO_SCRIPT_DIR=`dirname "$PATH_TO_SCRIPT"`

实际上,如果您不需要SCRIPT变量的值,那么您甚至可以将这两行合并为一行。但您为什么要为此付出努力呢?

Python被提到了几次。这是JavaScript(即Node.js)替代方案:

baseDirRelative=$(dirname "$0")baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # Get absolute path using Node.js
echo $baseDir

总结:

FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
# OR, if you do NOT need it to work for **sourced** scripts too:# FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# OR, depending on which path you want, in case of nested `source` calls# FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[0]}")"
# OR, add `-s` to NOT expand symlinks in the path:# FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

详情:

如何获得完整文件路径完整目录基本文件名的任何脚本是运行来源

…即使从另一个bash函数或脚本中调用被调用的脚本,或者正在使用嵌套源!

对于许多情况,您需要获取的只是您刚刚调用的脚本的完整路径。这可以使用realpath轻松完成。请注意,realpathGNUcoreutils的一部分。如果您尚未安装它(它在Ubuntu上是默认的),您可以使用sudo apt update && sudo apt install coreutils安装它。

get_script_path.sh(有关此脚本的最新版本,请参阅我的eRCaGuy_hello_world存储库中的get_script_path.sh):

#!/bin/bash
# A. Obtain the full path, and expand (walk down) symbolic links# A.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.# FULL_PATH_TO_SCRIPT="$(realpath "$0")"# A.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even# if the script is called from within another bash function!# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"# B.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.# FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "$0")"# B.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even# if the script is called from within another bash function!# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "${BASH_SOURCE[-1]}")"
# You can then also get the full path to the directory, and the base# filename, like this:SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"
# Now print it all outecho "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

"${BASH_SOURCE[-1]}"7如果上面的"${BASH_SOURCE[-1]}"没有给你想要的,试着用"${BASH_SOURCE[0]}"代替。第一个(0)索引给你数组中的第一个条目,最后一个(-1)索引给你数组中的最后一个条目。根据你想要的,你可能真的想要第一个条目。当我用. ~/.bashrc来采购~/.bashrc时,我发现了这种情况,它用. ~/.bash_aliases来采购~/.bash_aliases,我想要realpath(带有扩展的符号链接)到~/.bash_aliases文件,而不是~/.bashrc文件。由于这些是"${BASH_SOURCE[-1]}"8source调用,使用"${BASH_SOURCE[0]}"给了我我想要的:扩展到~/.bash_aliases的路径!然而,使用"${BASH_SOURCE[-1]}"给了我"${BASH_SOURCE[-1]}"9想要的东西:扩展到~/.bashrc的路径。

示例命令和输出:

  1. 运行脚本:
    ~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.shFULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"SCRIPT_FILENAME     = "get_script_path.sh"
  2. 采购带有. get_script_path.shsource get_script_path.sh的脚本(结果与上面完全相同,因为我在脚本中使用了"${BASH_SOURCE[-1]}"而不是"$0"):
    ~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.shFULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"SCRIPT_FILENAME     = "get_script_path.sh"

如果您在脚本中使用"$0"而不是"${BASH_SOURCE[-1]}",当脚本运行时,您将获得与上述相同的输出,但当脚本采购时,此不受欢迎的输出:

~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.shFULL_PATH_TO_SCRIPT               = "/bin/bash"SCRIPT_DIRECTORY                  = "/bin"SCRIPT_FILENAME                   = "bash"

显然,如果你使用"$BASH_SOURCE"而不是"${BASH_SOURCE[-1]}",如果从另一个bash函数中调用脚本,它将没有工作。因此,使用"${BASH_SOURCE[-1]}"是最好的方法,因为它解决了这两个问题!请参阅下面的参考资料。

realpathrealpath -s的区别:

请注意,realpath也成功地向下走符号链接以确定并指向它们的目标,而不是指向符号链接。如果你不想要这种行为(有时我不想),然后将-s添加到上面的realpath命令中,使该行看起来像这样:

# Obtain the full path, but do NOT expand (walk down) symbolic links; in# other words: **keep** the symlinks as part of the path!FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

这样,符号链接不会扩展。相反,它们保持原样,作为完整路径中的符号链接。

上面的代码现在是此文件中eRCaGuy_hello_world存储库的一部分:bash/get_script_path.sh。引用并运行此文件以获取路径中带有和带有OUT符号链接的完整示例。在这两种情况下,请参阅文件底部的示例输出。

参考文献:

  1. 如何检索给定相对的绝对路径
  2. 教我BASH_SOURCE变量:Unix&Linux:确定源外壳脚本的路径
  3. 告诉我BASH_SOURCE实际上是一个数组,我们希望它的最后一个元素在函数内按预期工作(因此我在这里的代码中使用"${BASH_SOURCE[-1]}"):Unix&Linux:确定源外壳脚本的路径
  4. man bash-->搜索BASH_SOURCE

    BASH_SOURCE

    一个数组变量,其成员是源文件名,其中定义了FUNCNAME数组变量中相应的shell函数名称。shell函数${FUNCNAME[$i]}在文件${BASH_SOURCE[$i]}中定义并从${BASH_SOURCE[$i+1]}调用。

另见:

  1. [我的回答]Unix&Linux:确定源外壳脚本的路径

保持简单。

#!/usr/bin/env bashsourceDir=`pwd`echo $sourceDir

我认为最简单的答案是原始变量的参数扩展:

#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"echo "opt1; original answer: $DIR"echo ''
echo "opt2; simple answer  : ${BASH_SOURCE[0]%/*}"

它应该产生如下输出:

$ /var/tmp/test.shopt1; original answer: /var/tmp
opt2; simple answer  : /var/tmp

变量/参数扩展${BASH_SOURCE[0]%/*}"似乎更容易维护。

这种方法的一个优点是它不涉及Bash本身之外的任何东西,也不分叉任何子shell。

首先,使用模式替换将任何不以/开头的内容(即相对路径)替换为$PWD/。由于我们使用了匹配#2的第一个字符的替换,我们还必须将其附加回来(替换中的${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}"}

这也适用于可执行脚本。它更长,但更多价。

我尝试了以下3种不同的处决。

echo $(realpath $_)

. application         # /correct/path/to/dir or /path/to/temporary_dirbash 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/dirbash application      # /correct/path/to/dir/PATH/TO/application  # /correct/path/to/dir

只有$(realpath $BASH_SOURCE)似乎是可靠的。

如果目录名末尾有任何换行符,当前的解决方案都不起作用-它们将被命令替换删除。要解决这个问题,您可以在命令替换中附加一个非换行符,然后仅删除该字符:

dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd && echo x)"dir="${dir%x}"

这可以防止两种非常常见的情况:事故和破坏。脚本不应该仅仅因为某个地方的某个人做了mkdir $'\n'而以不可预测的方式失败。

大多数答案要么不处理通过相对路径符号链接的文件,要么不是单行程序,要么不处理BSD(Mac)。完成所有这三个的解决方案是:

HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd)

首先,cd到bash对脚本目录的概念。然后重新链接文件以查看它是否是符号链接(相对或其他),如果是,cd到该目录。如果不是,cd到当前目录(保持单行代码所必需的)。然后通过pwd回显当前目录。

您可以将--添加到cd和readlink的参数中,以避免出现类似选项的目录问题,但我不会为大多数目的而烦恼。

你可以在这里看到完整的解释和插图:

https://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html

令人烦恼的是,这是我发现的唯一一个在可执行脚本是符号链接时适用于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

如果父脚本的没有来源没有符号链接$0就足够了:

script_path="$0"

如果父脚本来源没有符号链接,请使用#0或#1

script_path="$BASH_SOURCE"

如果符号连接,使用$BASH_SOURCE#1或#2来获取真实文件路径:

script_path="$(realpath "$BASH_SOURCE")"

此外,realpathreadlink -f返回绝对路径

要获取脚本的的目录,请使用dirname

script_directory="$(dirname "$script_path")"

说明

您可以通过以下简短方式从脚本本身中获取Bash脚本的源目录:

script_path=$(dirname "$(readlink -f "$0")")"/"echo "$script_path"

示例输出:

/home/username/desktop/

另一个变体:

SELF=$(SELF=$(dirname "$0") && bash -c "cd \"$SELF\" && pwd")echo "$SELF"

这也适用于macOS,确定规范路径,并且不会更改当前目录。