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

Posted

技术标签:

【中文标题】如何从脚本本身中获取 Bash 脚本的源目录?【英文标题】:How can I get the source directory of a Bash script from within the script itself? 【发布时间】:2010-09-08 18:17:52 【问题描述】:

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

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

$ ./application

【问题讨论】:

如果有任何 目录名称末尾的换行符,当前的解决方案都不起作用 - 它们将被命令替换剥离。要解决此问题,您可以在命令替换中附加一个非换行符 - DIR="$( cd "$( dirname "$BASH_SOURCE[0]" )" && pwd && echo x)" - 并在没有命令替换的情况下将其删除 - DIR="$DIR%x" @jpmc26 有两种非常常见的情况:事故和破坏。脚本不应该仅仅因为某人在某个地方做了mkdir $'\n',就以不可预知的方式失败。 任何让人们以这种方式破坏系统的人都不应该让 bash 来检测此类问题......更不用说雇用能够犯这种错误的人了。在使用 bash 的 25 年中,我从未见过这种事情发生在任何地方……这就是为什么我们有 perl 之类的东西和诸如污点检查之类的做法(我可能会因为这样说而被激怒:) 我强烈建议阅读这个Bash FAQ关于这个主题。 【参考方案1】:

如何获取正在运行的任何脚本的完整文件路径完整目录基本文件名

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

get_script_path.sh

#!/bin/bash

# A. Obtain the full path, and expand (walk down) symbolic links
FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# OR: B. 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_KEEP_SYMLINKS="$(realpath -s "$0")"

# 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 out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

示例输出:

~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh 
FULL_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"

请注意,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 "$0")"

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

上面的代码现在是我eRCaGuy_hello_world repo 的一部分,在这个文件中:bash/get_script_path.sh。参考并运行此文件以获取路径中带有和不带有符号链接的完整示例。两种情况下的示例输出见文件底部。

参考资料:

    How to retrieve absolute path given relative

【讨论】:

【参考方案2】:

这是一个易于记忆的脚本:

DIR=$(dirname "$BASH_SOURCE[0]")   # Get the directory name
DIR=$(realpath "$DIR")    # Resolve its full path if need be

【讨论】:

或者,更隐晦地,在一行中:DIR=$(realpath "$(dirname "$BASH_SOURCE[0]")") 为什么这不是公认的答案?使用realpath 与使用readlink 循环“手动”解析有什么区别?甚至readlink 手册页上都写着Note realpath(1) is the preferred command to use for canonicalization functionality. 顺便说一句,我们不应该在dirname之前应用realpath,而不是之后?如果脚本文件本身是一个符号链接......它会给出类似DIR="$(dirname "$(realpath "$BASH_SOURCE[0]")")" 的东西。实际上非常接近西蒙提出的答案。 @User9123 我认为接受的是尝试与所有流行的外壳/发行版兼容。此外,根据您要执行的操作,在大多数情况下,人们希望获取符号链接所在的目录,而不是实际源的目录。 唯一的原因是 mac 上缺少 coreutils。我正在使用SCRIPT=$(realpath "$BASH_SOURCE[0]") + DIR=$(dirname "$SCRIPT")【参考方案3】:
#!/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 )

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

注意:如果你在运行这个 sn-p 之前cd 到不同的目录,结果可能不正确!

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

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

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

【讨论】:

您可以将此方法与 user25866 的答案相结合,得出适用于 source <script>bash <script> 的解决方案:DIR="$(cd -P "$(dirname "$BASH_SOURCE[0]")" && pwd)" 有时cd 会打印一些内容到 STDOUT!例如,如果您的 $CDPATH 具有 .。要涵盖这种情况,请使用DIR="$( cd "$( dirname "$BASH_SOURCE[0]" )" > /dev/null && pwd )" 这个接受的答案不好,它不适用于符号链接并且过于复杂。 dirname $(readlink -f $0) 是正确的命令。测试用例见gist.github.com/tvlooy/cbfbdb111a4ebad8b93e @tvlooy IMO 您的答案也不完全正确,因为路径中有空格时它会失败。与换行符相比,这不是不可能的,甚至是不常见的。 dirname "$(readlink -f "$0")" 不会增加复杂性,而且对于最少的麻烦来说更稳健。【参考方案4】:
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 从调用者修改时。 "./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 以完整路径给出,无论它如何调用。

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

【讨论】:

不错!可以缩短用 SCRIPT_PATH=readlink -f $(dirname "$VIRTUAL_ENV") 替换 "pushd[...] popd /dev/null"; 这是迄今为止我见过的最“稳定”的版本。谢谢! 而不是使用 pushd ...;使用 $(cd dirname "$SCRIPT_PATH" && pwd) 不是更好吗?但无论如何,很棒的剧本! 脚本将cd 移出当前目录并希望稍后再返回cd 是很危险的:脚本可能无权将目录更改回当前目录当它被调用时。 (对于 pushd/popd 也是如此) readlink -f 是 GNU 特定的。 BSD readlink 没有那个选项。【参考方案5】:

应该这样做:

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

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

请参阅dirnamerealpath 的手册页。

请添加关于如何支持 MacOS 的评论。很抱歉,我可以验证一下。

【讨论】:

使用您的解决方案,调用像./script.sh 这样的脚本会显示. 而不是完整的目录路径 MacOS 上没有用于 readlink 的 -f 选项。请改用stat。但是,如果您在“this”目录中,它仍然显示. 您需要从 Homebrew 安装 coreutils 并使用 greadlink 在 MacOS 上获取 -f 选项,因为它是*BSD 而不是 Linux。 您应该在所有右侧添加双引号:DIR="$(dirname "$(readlink -f "$0")")" 使用realpath 代替readlink -f 可以在Linux 和macOS (BSD) 上运行:dir="$(dirname "$(realpath "$0")")"【参考方案6】:

没有 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 ...

从现在开始,include(...) 的使用将基于您脚本中之前的CURRENT_SCRIPT_DIR

这仅在您可以将所有 source 命令替换为 include 命令时才有效。如果你不能,那么你别无选择。至少在 Bash 解释器的开发人员做出明确的命令来请求当前正在运行的脚本目录路径之前。

我自己最接近此的实现: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_tacklelib

(搜索tkl_include函数)

【讨论】:

【参考方案7】:

尝试使用:

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

【讨论】:

我只想知道,为什么这种方式不好?这对我来说似乎没有坏处和正确的。谁能解释为什么它被否决? realpath 不是标准实用程序。 在 Linux 上,realpath 是一个标准实用程序(GNU coreutils 包的一部分),但它不是内置的 bash(即 bash 本身提供的功能)。如果您运行的是 Linux,则此方法可能会起作用,尽管我会将 $0 替换为 $BASH_SOURCE[0],以便此方法可以在任何地方使用,包括在函数中。 这个答案中的操作顺序是错误的。您需要首先解析符号链接,然后执行dirname,因为$0的最后一部分可能是指向不同文件的符号链接目录作为符号链接本身。这个答案中描述的解决方案只是获取它存储符号链接的目录的路径,而不是目标的目录。此外,此解决方案缺少引用。如果路径包含特殊字符,它将不起作用。 dir="$(realpath "$(dirname "$BASH_SOURCE[0]")")"【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

令人讨厌的是,当可执行脚本是符号链接时,这是我发现的唯一在 Linux 和 macOS 上都可以使用的单行代码:

SCRIPT_DIR=$(python -c "import os, sys; print(os.path.dirname(os.path.realpath('$BASH_SOURCE[0]')))")

在 Linux 和 macOS 上测试并与本要点中的其他解决方案进行比较:https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

【讨论】:

【参考方案10】:

顶部响应并非在所有情况下都有效...

由于我在通过“sh my_script.sh”调用 shell 脚本时在一些非常新鲜和不太新鲜安装的Ubuntu 16.04(Xenial Xerus)系统上使用包含“cd”方法的 BASH_SOURCE 存在问题,我尝试了一些不同的东西,到目前为止似乎运行得非常顺利。该方法在脚本中更加紧凑,并且更加不那么神秘。

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

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

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 变量的值,那么您甚至可以将这两条线合并成一行。但是你为什么真的要为此付出努力呢?

【讨论】:

这个问题是bash 特定的。如果您使用sh 调用脚本,则shell 可能是别的东西,例如zshdash 我现在不会检查它的代码 - 但如果你愿意,你可以用“bash”调用它。将“sh”视为兼容 shell 执行程序的基于二进制选择的别名。【参考方案11】:

大多数答案要么不处理通过相对路径进行符号链接的文件,要么不处理单行文件,要么不处理 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

【讨论】:

【参考方案12】:

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

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

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

【讨论】:

【参考方案13】:

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

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

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

#!/usr/bin/env sh

dir=$(cd "$0%[/\\]*" > /dev/null && pwd)

if [ -d /proc/cygdrive ]; then
    case "$(uname -s)" in
        CYGWIN*|MINGW32*|MSYS*|MINGW*)
            # We are under Windows, so translate path to Windows format.
            dir=$(cygpath -m "$dir");
            ;;
    esac
fi

# Runs the executable which is beside this script
"$dir/my-sample-app" "$@"

【讨论】:

什么是“Windows php”? “PHP For Windows”?还是WAMP?还是别的什么? 在another post 的帮助下,不再需要 PHP(现在检查是否在 Windows 下运行)。【参考方案14】:

我尝试了以下 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) 是可靠的。

【讨论】:

【参考方案15】:

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

首先,使用模式替换将不以/ 开头的任何内容(即相对路径)替换为$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"

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

【讨论】:

【参考方案16】:

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

#!/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.sh
opt1; original answer: /var/tmp

opt2; simple answer  : /var/tmp

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

【讨论】:

【参考方案17】:

Python 被多次提及。这是 javascript(即Node.js)替代方案:

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

echo $baseDir

【讨论】:

【参考方案18】:

这是我多年来精心设计的,用作我的 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 Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
    [ "$fa:0:1" = "/" ] && wa=$0 && break
    [ "$fa:0:2" = "./" ] && ta="$ORIGINAL_DIR/$fa:2" && [ -e "$ta" ] && wa="$ta" && break
    ta="$ORIGINAL_DIR/$fa" && [ -e "$ta" ] && wa="$ta" && break
done
SW="$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 包含您需要的内容。

【讨论】:

【参考方案19】:

您只需将脚本名称 ($0) 与 realpath 和/或 dirname 组合起来即可。它适用于 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. Special Variable Types

一个例子:LozanoMatheus/get_script_paths.sh

【讨论】:

【参考方案20】:

我想对上面的上一个答案发表评论 (How can I get the source directory of a Bash script from within the script itself?),但没有足够的声望。

两年前我在 Apple 的文档网站上找到了解决方案: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" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi

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

【讨论】:

【参考方案21】:

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

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

工作原理

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

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

后备替换运算符:-

您已经知道$... 替换字符串中的变量。您可能不知道在替换期间可以对变量进行某些操作(在Bash 和zsh 中),例如后备扩展运算符:-

% x=ok
% echo "$x"
ok

% echo "$x:-fallback"
ok

% x=
% echo "$x:-fallback"
fallback

% y=yvalue
% echo "$x:-$y"
yvalue

%x 提示符转义码

接下来,我们将介绍提示转义码,这是 zsh 独有的功能。在 zsh 中,%x 将扩展为文件的路径,但通常只有在对 prompt strings 进行扩展时才会这样做。为了在我们的替换中启用这些代码,我们可以在变量名前添加一个(%) 标志:

% cat apath/test.sh
fpath=%x
echo "$(%)fpath"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

不太可能的匹配:百分比转义和后备

到目前为止我们所做的工作,但避免创建额外的fpath 变量会更整洁。除了将%x 放入fpath 之外,我们还可以使用:- 并将%x 放入备用字符串中:

% cat test.sh
echo "$(%):-%x"

% source test.sh
test.sh

请注意,我们通常会将变量名放在(%):- 之间,但我们将其留空。空名的变量不能被声明或设置,所以总是触发回退。

完成:print -P %x 呢?

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

% cat apath/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "$(%):-%x"

% source apath/test.sh
apath
apath

事实证明,hacky 方式更加高效和简洁。

【讨论】:

【参考方案22】:

我通常使用:

dirname $(which $BASH_SOURCE)

【讨论】:

【参考方案23】:

chosen answer 运行良好。我将我的解决方案发布给任何寻找更短的替代方案的人,这些替代方案仍然可以解决采购、执行、完整路径、相对路径和符号链接。最后,这将适用于 macOS,因为不能假设 GNU 的 coreutils 版本的 readlink 可用。

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

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

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

【讨论】:

问题中没有关于 MacOS 的内容。我认为这个答案应该从“另一种方法是使用 PHP 而不是依赖 BASH”开始。因为这仅在答案的末尾显示。【参考方案24】:

如果您的 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 脚本的目录(原始文件,而不是符号链接)。我向上帝发誓这是行得通的,而且这是我知道的唯一正确执行此操作的方法。

【讨论】:

【参考方案25】:

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

#!/bin/bash
echo "$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

【讨论】:

【参考方案26】:

这个one-liner 可以在Cygwin 上工作,即使脚本是从Windows 中使用bash -c &lt;script&gt; 调用的:

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

【讨论】:

【参考方案27】:

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

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

【讨论】:

【参考方案28】:

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

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

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

还有一些我不会讨论的隐含期望(查看this answer),并且我不会尝试处理对$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 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
)

【讨论】:

【参考方案29】:
FOLDERNAME=$PWD##*/

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

【讨论】:

这只是从密码开始,不会返回当前正在执行的脚本所在的路径。【参考方案30】:

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

我发现无论文件是作为源文件还是作为脚本运行,此行始终有效。

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

如果您想跟踪符号链接,请在上面的路径上使用readlink,递归或非递归。

这里有一个脚本可以试用它并将其与其他建议的解决方案进行比较。以source test1/test2/test_script.shbash 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 数组变量中的相应 shell 函数名称。 shell 函数 $FUNCNAME[$i] 在文件 $BASH_SOURCE[$i] 中定义并从 $BASH_SOURCE[$i+1] 调用。

函数名

一个数组变量,包含当前在执行调用堆栈中的所有 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 手册]

【讨论】:

以上是关于如何从脚本本身中获取 Bash 脚本的源目录?的主要内容,如果未能解决你的问题,请参考以下文章

text 从脚本本身获取Bash脚本的源目录

如何从 bash 获取 telnet 命令的输出?

如何从sqlplus到bash脚本获取SQL查询输出

被另一个bash脚本调用后获取文件的当前目录[重复]

在 bash 脚本中获取祖父目录 - 为路径中的目录重命名文件

linux shell脚本获取脚本目录时,$(dirname “${BASH_SOURCE[0]}“)与$(dirname $0)有什么区别?(脚本路径,脚本包含关系,父子脚本)