【问题标题】:How can I get the source directory of a Bash script from within the script itself?如何从脚本本身中获取 Bash 脚本的源目录?
【发布时间】: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关于这个主题。

标签: bash directory


【解决方案1】:
#!/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")" 不会增加复杂性,而且对于最少的麻烦来说是更稳健的衡量标准。
【解决方案2】:

使用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

【讨论】:

  • 对于 bash 以外的可移植性,$0 可能并不总是足够的。如果在路径上找到该命令,您可能需要替换“type -p $0”以使其工作。
  • @Darron:如果脚本是可执行的,你只能使用type -p。如果使用bash test2.sh 执行脚本并且在其他地方有另一个同名的可执行脚本,这也会打开一个微妙的漏洞。
  • @Darron:但由于这个问题被标记为bash,并且 hash-bang 行明确提到了/bin/bash,我想说依赖 bashisms 是很安全的。
  • +1,但是使用dirname $0 的问题是如果目录是当前目录,你会得到.。这很好,除非您要更改脚本中的目录并期望使用从dirname $0 获得的路径,就好像它是绝对的一样。要获得绝对路径:pushd `dirname $0` > /dev/nullSCRIPTPATH=`pwd`popd > /dev/nullpastie.org/1489386(但肯定有更好的方法来扩展该路径?)
  • @T.J. Crowder 我不确定dirname $0 是否有问题,如果你将它分配给一个变量然后用它来启动像$dir/script.sh 这样的脚本;我想这是 90% 的时间这类事情的用例。 ./script.sh 可以正常工作。
【解决方案3】:

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

dirname "$0"

但是,正如 ma​​tt b 所指出的,返回的路径会根据脚本的调用方式而有所不同。 pwd 没有完成这项工作,因为它只告诉您当前目录是什么,而不是脚本所在的目录。此外,如果执行脚本的符号链接,您将得到一个(可能是相对的) 链接所在的路径,而不是实际的脚本。

其他人提到了readlink 命令,但最简单的,您可以使用:

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

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

这是一个演示每一个的脚本,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

【讨论】:

  • readlink 在默认安装的某些平台上不可用。如果可以,尽量避免使用它
  • 小心引用所有内容以避免出现空格问题:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
  • 在 OSX Yosemite 10.10.1 中,-f 未被识别为readlink 的选项。使用 stat -f 代替了这项工作。谢谢
  • 在OSX中,有greadlink,基本上就是我们都熟悉的readlink。这是一个独立于平台的版本:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
  • 好电话,@robert。仅供参考,greadlink 可以通过自制软件轻松安装:brew install coreutils
【解决方案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】:

简答:

`dirname $0`

或(preferably):

$(dirname "$0")

【讨论】:

  • 如果您获取脚本,它将无法工作。 “源我/script.sh”
  • 我一直在我的 bash 脚本中使用它,这些脚本可以自动执行一些操作,并且经常在同一个目录中调用其他脚本。我永远不会在这些上使用source,而cd $(dirname $0) 很容易记住。
  • @vidstige: ${BASH_SOURCE[0]} 而不是 $0 将与 source my/script.sh 一起使用
  • @TimothyJones 如果来自 bash 以外的任何其他 shell,则 100% 的时间将失败。 ${BASH_SOURCE[0]} 一点也不令人满意。 ${BASH_SOURCE:-0} 好多了。
【解决方案6】:

你可以使用$BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

【讨论】:

  • 当我做./foo/script,那么$(dirname $BASH_SOURCE)就是./foo
  • @Till,在这种情况下,我们可以使用realpath 命令获取./foo/script 的完整路径。所以dirname $(realpath ./foo/script) 会给出脚本的路径。
  • Backticks are deprecated. 改用美元括号 -- $(...).
【解决方案7】:

应该这样做:

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")")"
【解决方案8】:

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

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")
【解决方案9】:

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`

解决了问题中的具体场景,我觉得有绝对路径更普遍更有用。

【讨论】:

  • 您可以像这样在一行中完成所有操作:DIRECTORY=$(cd dirname $0 && pwd)
  • 如果脚本来源另一个脚本并且您想知道后者的名称,这不起作用。
【解决方案10】:

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

./script

/usr/bin/script

script

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

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

【讨论】:

  • 如果您打算反驳他的评论,请通过代码示例证明脚本可以访问它的存储位置。
【解决方案11】:
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

【讨论】:

  • 这比选择的答案要短得多。并且似乎也可以正常工作。这值得 1000 票,只是为了让人们不要忽视它。
  • 正如许多前面的答案所详细解释的那样,$0pwd 都不能保证拥有正确的信息,具体取决于调用脚本的方式。
【解决方案12】:
$(dirname "$(readlink -f "$BASH_SOURCE")")

【讨论】:

  • 我更喜欢 $BASH_SOURCE 而不是 $0 ,因为即使对于不精通 bash 的读者来说,它也是明确的。 $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
【解决方案13】:

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

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

【讨论】:

  • 所以这在 Linux 上不起作用?
【解决方案14】:

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

SELF=$(readlink /proc/$$/fd/255)

【讨论】:

  • 这也是 bash 特有的,但也许 bash 的行为已经改变了? /proc/fd/$$/255 似乎指向 tty,而不是目录。例如,在我当前的登录 shell 中,文件描述符 0、1、2 和 255 都引用了/dev/pts/4。在任何情况下,bash 手册都没有提到 fd 255,因此依赖这种行为可能是不明智的。\
  • 交互式外壳 != 脚本。无论如何realpath ${BASH_SOURCE[0]}; 似乎是最好的选择。
【解决方案15】:

这是一个符合 POSIX 标准的单行:

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

# test
echo $SCRIPT_PATH

【讨论】:

  • 在单独运行脚本或使用 sudo 时,我成功了,但在调用 source ./script.sh 时却没有
  • cd被配置为打印新路径名时会失败。
【解决方案16】:

执行此操作的最短和最优雅的方法是:

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

这适用于所有平台并且非常干净。

更多详情请见“Which directory is that bash script in?”。

【讨论】:

  • 非常干净的解决方案,但如果文件是符号链接的,这将不起作用。
【解决方案17】:
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`

【讨论】:

  • 我没有在不同的系统上测试过。但对我来说,这个解决方案至少可以在 Ubuntu 上立即运行!
【解决方案18】:

这是简单而正确的方法:

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 安装 GNU coreutils 并用 greadlink -f 代替它。

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

【讨论】:

  • greadlink -f 不幸的是在 Mac 上sourceing 脚本时无法有效工作:(
【解决方案19】:

我尝试了所有这些,但都没有奏效。一个非常接近,但它有一个小虫子把它弄坏了;他们忘记将路径用引号括起来。

还有很多人认为你是从 shell 运行脚本,所以他们忘记了当你打开一个默认为你家的新脚本时。

试试这个目录的大小:

/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"`"

【讨论】:

  • 如果脚本来自另一个脚本,则不起作用。
  • 如果 $0 的最后一部分是指向另一个目录 (ln -s ../bin64/foo /usr/bin/foo) 条目的符号链接,这将不起作用。
【解决方案20】:

这是对their answer中指出的解决方案e-satis和3bcdnlklvc04a的轻微修改:

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

这应该在他们列出的所有情况下仍然有效。

这将在 pushd 失败后阻止 popd。感谢 konsolebox。

【讨论】:

  • 这可以完美地获取“真实”目录名,而不仅仅是符号链接的名称。谢谢!
  • 更好SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
  • @konsolebox,你想防御什么?我通常是内联逻辑条件的粉丝,但是您在 pushd 中看到的具体错误是什么?我宁愿找到一种直接处理它的方法,而不是返回一个空的 SCRIPT_DIR。
  • @Fuwjax 在pushd 失败的情况下(即使很少见)避免使用popd 的自然做法。如果pushd 失败,您认为SCRIPT_DIR 的值应该是多少?该操作可能会有所不同,具体取决于看起来合乎逻辑的内容或用户可能喜欢的内容,但可以肯定的是,popd 是错误的。
  • 所有这些pushd popd 的危险都可以通过删除它们并使用cd + pwd 代替命令替换来避免。 SCRIPT_DIR=$(...)
【解决方案21】:

我会使用这样的东西:

# 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

【讨论】:

  • 这是真的!也适用于简单的sh!基于dirname "$0" 的简单解决方案的问题:如果脚本在$PATH 中并且在没有路径的情况下调用,它们将给出错误的结果。
  • @Notinlist 不是这样。如果通过PATH 找到脚本,$0 将包含绝对文件名。如果使用包含/ 的相对或绝对文件名调用脚本,$0 将包含该文件。
【解决方案22】:

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

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

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

【讨论】:

  • 除非脚本的来源是 .或 'source' 在这种情况下,它仍然是任何脚本,或者,如果从命令行,'-bash'(tty 登录)或'bash'(通过'bash -l'调用)或'/bin/ bash'(作为交互式非登录 shell 调用)
  • 我在dirname 通话周围添加了第二对引号。如果目录路径包含空格,则需要。
【解决方案23】:

尝试使用:

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

【讨论】:

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

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

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

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

【讨论】:

  • 如果你 source. 脚​​本会中断。在这些情况下,$_ 将包含您在. 之前运行的最后一个命令的最后一个参数。 $BASH_SOURCE 每次都有效。
  • 这是Perl-like!巧合?
【解决方案25】:

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

文件夹和文件:

    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")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

我得到了这个输出:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

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

【讨论】:

  • 我认为我的回答还可以,因为很难找到一个简单的工作版本。在这里,您可以获取您喜欢的代码,例如cd + pwd、dirname + realpath 或 dirname + readlink。我不确定所有部分之前都存在,并且大多数答案都很复杂且超载。在这里,您可以提取您喜欢使用的代码。至少请不要删除它,因为我将来需要:D
【解决方案26】:

这适用于 Bash 3.2:

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

如果你的$PATH 中有一个~/bin 目录,那么这个目录中就有A。它获取脚本~/bin/lib/B。您知道包含的脚本相对于原始脚本的位置,在 lib 子目录中,但不知道它相对于用户当前目录的位置。

这可以通过以下方式解决(在A 内):

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

用户在哪里或他/她如何调用脚本并不重要。这将始终有效。

【讨论】:

  • which 的观点非常值得商榷。 typehash 和其他内置函数在 bash 中做得更好。 which 更便于携带,尽管它与其他 shell(如 tcsh)中使用的 which 不一样,它具有内置功能。
  • “总是”?一点也不。 which 作为外部工具,您没有理由相信它的行为与父 shell 相同。
【解决方案27】:

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

在许多情况下,您只需要获取刚刚调用的脚本的完整路径。这可以使用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。参考并运行此文件以获取路径中带有和不带有符号链接的完整示例。两种情况下的示例输出见文件底部。

参考资料:

  1. How to retrieve absolute path given relative

【讨论】:

    【解决方案28】:

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

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

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

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

    这有点难看,但修复紧凑且易于理解。我们不只使用 bash 原语,但我可以接受,因为 readlink 大大简化了任务。 echo XX 添加到变量字符串的末尾,这样文件名中的任何尾随空格都不会被吃掉,并且行尾的参数替换${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"
    

    【讨论】:

    • 我在 Ubuntu 14.10 桌面上使用 bash 得到 /dev/pts/30
    • @DanDascalescu 使用单线?还是底部的完整代码sn-p?你是否给它提供了任何棘手的路径名?
    • 一行加另一行到echo $resolved,我保存为dchmod +x d./d
    • @DanDascalescu 脚本中的第一行必须是 #!/bin/bash
    【解决方案29】:

    我相信我有这个。我参加聚会迟到了,但我想有些人如果遇到这个帖子会很高兴来到这里。 cmets应该解释:

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

    【讨论】:

      【解决方案30】:

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

      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变量+文件名参数(不带./前缀)。

      相关:

      【讨论】:

      • 请详细解释一下realpath函数。
      • @Chris realpath 函数接受 1 个参数。如果参数已经有绝对路径,则按原样打印,否则打印$PWD + 文件名(不带./ 前缀)。
      • 当脚本被符号链接时,您的交叉兼容解决方案不起作用。
      猜你喜欢
      • 1970-01-01
      • 2014-06-30
      • 2022-06-16
      • 1970-01-01
      • 2013-11-06
      相关资源
      最近更新 更多