假设我有一个文档项目,位于 /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)"
../..
至此,问题似乎完全得到了解决。
没错。
虽然我很想写一个与上述命令等价的功能……
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。