项目根目录的第N个子目录中的bash脚本如何可靠地知道项目根目录的绝对路径?

Lif*_*ign 2 git directory bash shell

注意:我非常努力地确保这不是重复的,并且没有问过这个问题bash。据我所知,它不是重复的。

因为我对这样做的所有方式都感兴趣,并且由于git我的 OP 中强调这是一个回购,所以我不会要求答案仅使用 SHELL BUILTIN 命令,但会优先考虑仅使用的答案shell 内置函数。

我在问题中包含的脚本是我使用纯 shell 脚本回答问题的最佳尝试。

语境

我有一个目录结构复杂的大型 bash 项目。让我们bash先把我为什么选择的问题放在一边,而不是像python.

git用于对项目进行版本控制,该项目应该能够在用户空间的任何位置分发和安装并正常运行。因此,我有以下要求:

要求

  1. 我希望能够将各种脚本移动到不同的目录中,而不会破坏或改变它们的运行方式。在这种情况下,可以修改脚本的执行方式(例如,通过使用其他脚本中正确的更新路径),但是在将脚本移动到新目录后,不必重写脚本的内部结构,以确保它继续工作。

  2. 我希望每个脚本都source来自vars.sh项目根目录中的一个文件中的重要变量,这样如果我决定更改项目的目录结构,我就不必在每个脚本中编辑这些变量。因此,每个脚本都需要知道项目根目录在哪里。

此类变量的示例:包含项目内目录config绝对路径或目录绝对路径等的变量tmp)。这是确定要重新写vars.sh,如果目录结构被改变,当然,本身。但我们的想法是需要重写vars.sh(如果脚本被移动,则适当地调用脚本)。

假设

  1. 我在用git,“项目根”可以定义为git的项目树根,即有目录的.git目录。
  2. 在脚本中,任何脚本都不需要被告知它有多少个子目录。

讨论和提议的解决方案

据我所知,要求2任何脚本source变量和/或函数本身都需要回答这篇文章的主要问题。根据我的经验,source使用相对路径名的命令,例如

source "../../vars.sh"
Run Code Online (Sandbox Code Playgroud)

产生一个错误“没有这样的文件或目录”bash,并侵犯了我的假设2和要求1

请注意,DIR通过查找脚本目录的绝对路径创建的变量,使用

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
Run Code Online (Sandbox Code Playgroud)

如果放置在vars.sh项目根目录中,将根据源脚本所在的位置进行不同的评估。因此,它很有用,但我们仍然需要找到项目根目录的静态绝对路径。

我的解决方案的想法是在每个脚本中包含一个函数,该函数while使用一系列cd命令(类似于DIR上面的扩展方式)执行循环,以在项目中逐步向上导航,直到test存在目录.git提供循环中断,此时包含.git目录的目录被定义为项目根并分配给一个变量。该函数将小心避免评估用户主目录中子目录之外的任何目录:

#!/bin/bash

# a script that implements a single function to determine the root directory of
# a project. The root directory is assumed to be the one containing a .git
# directory, and is required to be in a subdirectory of $HOME.

# get the absolute path of the directory containing this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

find_root () {
    # find the project's root directory, based on git the existence of a .git
    # directory
    local current_dir
    current_dir=$DIR
    test_dir=".git"

    # make sure we are somewhere in a subdirectory of the user's home directory
    # if we aren't, make a suggestion and exit 
    if [[ ! "$current_dir" =~ ^${HOME}/.+ ]]; then
        echo "$(basename $0): Detected that we are not within a subdirectory of
        the home directory ${HOME}.  Please install the project containing this
        script somewhere safer, in ${HOME}.  Exiting" >&2
        exit 1
    fi

    # otherwise, continue with the directory examination
    while true; do
        # if the test directory is found, assign the root directory and stop
        # checking directories by breaking out of the loop
        if [ -d "${current_dir}/${test_dir}" ]; then
            root_dir="${current_dir}"
            echo "Found the project root: ${root_dir}"
            break

        # otherwise, continue cd'ing upwards as long as the parent directory
        # isn't the users's home directory, at which point we stop checking and
        # exit the script with an error
        else
            parent_dir="$(dirname "$current_dir")"
            if [[ "${parent_dir}" == "${HOME}" ]]; then
                echo "Couldn't find a directory ${test_dir} in a subdirectory
                of ${HOME} in order to confirm the root directory of ${0}.
                Exiting" >&2
                exit 1

            # if we can cd to the parent directory, continue the search there
            # remember to set the current directory to the parent directory;
            # simply cd'ing there doesn't have any effect, since this loops
            # evaluates the parameter current_dir, not $(pwd)
            elif cd $parent_dir; then 
                echo "Examining $parent_dir for $test_dir"
                current_dir=$parent_dir
                continue
            else
                echo "Couldn't cd to the parent directory. Exiting." >&2
                exit 1
            fi
        fi
    done

    # declare ROOT_DIR read only to prevent any subsequent assignment to it,
    # and export it for child processes
    echo "Setting ROOT_DIR to ${root_dir} as read only, and exporting ROOT_DIR"
    declare -r ROOT_DIR="$root_dir"
    export ROOT_DIR

    return
}

#execute the function
find_root
Run Code Online (Sandbox Code Playgroud)

我选择不“回答您自己的问题”,因为我想知道您对以下方面的看法:

  1. 这种方法的优缺点,或存在的缺陷
  2. 完全回答问题并解决问题的其他解决方案,给定要求
  3. 这种方法的改进

请注意,在我上面的示例中,.git对定义项目根目录的目录进行测试的选择是任意的。它可以很容易地是任何文件,例如.root_file,具有自定义权限或属性的文件,只要它可以由test.

bk2*_*204 5

任何 Git 存储库的工作树的绝对路径都可以通过git rev-parse --show-toplevel. 这也将是一个规范路径:它将解析所有符号链接,而在 Windows 上,它将以其他方式规范(例如, resolve SUBST)。即使.git目录位于其他地方(例如,使用GIT_DIR)、在子模块中(其中.git可能是文件)或在工作树中,它也可以工作。

如果您知道您的项目将始终在存储库中,那么您可以简单地执行以下操作:

. "$(git rev-parse --show-toplevel)/vars.sh"
Run Code Online (Sandbox Code Playgroud)