假设我有一个文档项目,位于 /tmp/demo 目录,该目录称为文档项目的根目录,其结构部署如下:

/tmp/demo
├── gar.conf
├── images
│   └── foo
├── output
│   └── foo
└── source
    └── foo

假设此刻我的工作目录是 /tmp/demo/source/foo,该目录相对于 /tmp/demo 的路径显然是 source/foo,后者相对于前者的路径显然是 ../..,但是在 Bash 脚本里,该如何构造这样的路径呢?这个问题,在我写 gar 脚本时,是一个很关键的技术问题,因为在此我仅仅是举例说 /tmp/demo 是我的文档项目的目录,事实上,这个目录并不固定,它是可变的,而且我所写的 gar 脚本在任何一个我具有读写权限的目录内创建上述结构的文档目录。

首先,我要设法从工作目录回溯到文档项目的根目录,在本文示例里,即从 /tmp/demo/source/foo 回溯到 /tmp/demo,否则我仅能知道工作目录的绝对路径——$(pwd),但不知文档项目根目录的绝对路径。该回溯过程必须具备一般性,因为工作目录也并不固定,例如,它完全也可以是

/tmp/demo/source/foo/a/b/c/d/e/f/...

由于 Bash 脚本支持递归函数,因此实现上述的目录回溯并不困难,例如:

function goto_root {
    cd ..
    goto_root
}

关键是,这个递归函数何时停止。由于文档项目的根目录里始终都有一个固定的文件 gar.conf,可利用该文件设定递归停止条件,因而 goto_root 函数可定义为

function goto_root {
    if [ -e gar.conf ]
    then
        echo "$(pwd)"
    else
        cd ..
        goto_root
    fi
}

倘若工作目录并非 /tmp/demo/source/foo 这样的目录,而是因为某些原因,工作目录切换到了 /tmp/demo 之外的某个目录,但是因为粗心,忘记了此事。在这种情况下,执行上述的 goto_root,会因无法找到 gar.conf 文件,而导致目录回溯到系统的根目录 /,然后导致递归过程不断地在 / 目录下 cd ..,从而导致递归过程因陷入僵局而崩溃。所以,需要在 goto_root 里再增加一个返回条件避免这种情况,因而 goto_root 更好的定义如下:

function goto_root {
    if [ -e gar.conf ]
    then
        echo "$(pwd)"
    elif [ "$(pwd)" = "/" ]
    then
        echo "root of gar not found!"
        exit 1
    else
        cd ..
        goto_root
    fi
}

通过 goto_root 函数,在文档项目的任一子目录下,皆可回溯到文档的根目录。通过 Bash 的命令替换,则可得到文档项目根目录的绝对路径:

demo_root="$(goto_root)"

注意,上述代码中,因为顾虑目录名会包含空格,安全起见,用了双引号包含了目录变量。这是我多次受到空格的教训之后,变乖了……

接下来,便可以考虑如何基于工作目录 "$(pwd)" 和文档项目根目录 "$demo_root" 构造相对路径的问题。我知道,GNU coreutils 提供了 realpath 命令,基于它可输出一个目录相对于另一个目录的路径,例如:

$ realpath "$(pwd)" --relative-to="$demo_root"
source/foo

$ realpath "$demo_root" --relative-to="$(pwd)"
../..

至此,问题似乎完全得到了解决。

没错。

虽然我很想写一个与上述命令等价的功能……


garfileo
6k 声望1.9k 粉丝

这里可能不会再更新了。


引用和评论

0 条评论