一线确定当前正在运行的bash脚本的目录

时间:2018-10-22 08:38:41

标签: bash

此问题与以下内容有关:Getting the source directory of a Bash script from within-在该问题中,有一个完美的答案,该结果显示show可以获取当前正在运行的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 && 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 && pwd )"

这很完美,除了它有很多样板代码。我正在处理一个脚本包,其中包含许多互相使用的小脚本。此脚本包托管在git repo中,并且它必须与位置无关。例如。无论克隆到哪个目录,它都应始终以相同的方式工作。一个人必须能够将此仓库克隆到许多不同的目录中,并独立使用它们。许多脚本只是命令的包装,或者它们在同一目录中或相对于包含目录的调用其他脚本。例如,以下命令模拟“ mongo”命令,但在docker容器中运行该命令:

#!/bin/bash

set -e

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 && 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 && pwd )"
# mongo-container is another script that determines the container id
# and it must be referenced relatively to the containing directory
MONGOE="docker exec -ti `${DIR}/mongo-container`"
${MONGOE} mongo "$@"

此代码的90%用于确定脚本的目录,只有10%的代码有用。我大约有20个这样的脚本。这意味着我的bash脚本中有90%只是“确定约束目录”而没有做任何事情。当我看着它们时,我几乎看不到它们在做什么,因为有很多样板代码。必须有更好的方法,我只是想不通。

有什么想法吗?

3 个答案:

答案 0 :(得分:2)

也许您想在专用的通用GNU / Bash脚本中“分解”此源代码?

首先,我认为您可以使用 which 命令,该命令在大多数GNU操作系统上可用(通常无需额外安装)

https://savannah.gnu.org/projects/which/

然后,您可以创建一个“常用功能”文件,其中包含用于定义完整路径的源代码,包括符号链接管理,我们将其称为 /tmp/myTrueDir/common.sh :< / p>

#!/bin/bash

set -e

# Usage: resolveCompleteAbsolutePath <$0 path to regard>
function resolveCompleteAbsolutePath() {
  local SOURCE="$1"
  while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
    DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null && 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 && pwd )"

  echo "$DIR"
}

然后,您可以在所有小脚本的开头使用它(首先,我们不必在意管理符号链接以访问位于脚本同一目录中的common.sh文件)

currentDir=$( dirname "$( which "$0" )" )
source "$currentDir/common.sh"

最终,您可以使用 resolveCompleteAbsolutePath 函数(在 common.sh 文件中定义)来获取完整的路径,包括符号链接解析。

这样,您的脚本将仅包含所需的有趣源代码,并且路径管理将在同一位置分解。

例如,您可以使用此文件系统结构轻松测试所有这些:

/tmp/myTrueDir/
/tmp/myTrueDir/common.sh
/tmp/myTrueDir/test.sh

使用 /tmp/myTrueDir/test.sh 文件包含以下示例行:

#!/bin/bash

currentDir=$( dirname "$( which "$0" )" )
source "$currentDir/common.sh"
resolvedPath=$( resolveCompleteAbsolutePath "$0" )

echo "currentDir: $currentDir"
echo "resolvedPath: $resolvedPath"

然后,您可以创建一个指向真实目录的符号链接,假设:

ln -s /tmp/myTrueDir /tmp/mySymbDir

然后,您可以从符号路径(即/tmp/mySymbDir/test.sh)中调用测试脚本,并根据需要查看其工作方式:

currentDir: /tmp/mySymbDir
resolvedDir: /tmp/myTrueDir

答案 1 :(得分:2)

请参见BashFAQ/028 (How do I determine the location of my script? ...),以更好地解决此问题。特别要注意第二段:“重要的是要意识到,在一般情况下,此问题没有解决方案。您可能听说过的任何方法以及下面将详细介绍的任何方法都存在缺陷,并且只能在以下情况下起作用:具体情况。首先,请尽量不通过不依赖脚本位置来完全避免此问题!”

可能是您的情况如此,所以可能有足够好的解决方案。如果您的系统具有支持readlink选项的-e,请尝试:

prog_realpath=$(readlink -e -- "${BASH_SOURCE[0]}")
prog_realdir=${prog_realpath%/*}/

请注意,如果程序的“真实”路径以换行符结尾,则prog_realpath中的值将是错误的。这可以解决,但这是极不可能的情况,并且不会阻止prog_realdir中的值正确。

请参阅Correct Bash and shell script variable capitalization,以了解为什么我更改了SOURCEDIR的名称。

/值上的尾随prog_realdir是为了确保即使程序位于根目录中也有效。使用dirname来获取目录将避免/问题,但除非使用其他技巧,否则很容易受到尾随换行符问题的影响。有关尾随换行符问题的更多信息,请参见shell: keep trailing newlines ('\n') in command substitution

如果您的系统不(全部)支持readlink -e,则可以改为使用realpath。有关readlinkrealpath在多个操作系统上的可用性的更多信息,请参见What's the difference between “realpath” and “readlink -f”

答案 2 :(得分:1)

我倾向于在代码中添加以下行

PROGDIR=$(cd $(dirname $0) && pwd)

这将更改为脚本的目录,然后运行pwd以获取目录路径。我从来没有遇到过这个问题。

希望这会有所帮助。