给出两条绝对路径,例如
/a/path/to/a
/a/path/to/somewhere/else
我怎样才能获得从一个到另一个的相对路径,../a
?
从某种意义上说,与realpath
的作用相反。
我在这里回答了类似的问题:Resolving a relative path without referencing the current directory on Windows。
这没有标准功能。为此,vi-like-emacs中有一个函数。快速检查apropos relative
显示我很少有其他程序可能实现这个:例如revpath)。
它可以作为字符串操作(不需要计算工作目录)来完成:
第二步中的“完成”假定您想使用相对路径来缩短结果。另一方面,您可能希望使用相对路径名,而不管长度如何。在这种情况下,只需跳过该步骤(结果将更长,但相对)。
找到最长的公共路径(在本例中为/a/path/to
)并从两个绝对路径中删除它。这会给:
/a
/somewhere/else
现在,使用../
替换起始路径中的每个路径组件,并将结果添加到目标路径。如果你想从else
目录转到目录a
,那会给你:
../../a
如果你想走另一条路,你会改为:
../somewhere/else
使用第一个绝对路径构建树,然后将第二个路径添加到该树,然后从一个叶子走到另一个叶子:从一个节点到其父节点的步骤被转换为“../”序列和一个步骤从节点到其子节点之一被转换为该子节点的名称。请注意,可能有多个解决方案。例如:
1)/a/path/to/a
和
2)/a/path/to/a/new/one
从(1)到(2)的明显路径是new/one
,但../../../a/path/to/a/new/one
也是有效的。当您编写算法来在树中行走时,您必须意识到这一点
这是在“ln”命令中实现的,这是GNU Coreutils包的一部分(对于ln -r
)。显然,有很多方法可以解决这个问题,甚至可以从自己提出解决方案中获得一些好处,而无需查看现有代码。就个人而言,我发现Coreutils中的代码具有相当的指导性。
如果我必须将绝对路径转换为C项目中的相对路径,我只需从Coreutils复制“relpath.c”。它与包中的其他实用程序函数有一些依赖关系,这些也需要以某种形式引入。这是主要的“relpath()”函数。请注意,它适用于规范化的路径名,例如,它不喜欢路径包含“//”或“/。”之类的东西。基本上它找到两个路径的公共前缀,然后它根据每个路径的剩余部分是否为空来处理出现的四种情况。
/* Output the relative representation if possible.
If BUF is non-NULL, write to that buffer rather than to stdout. */
bool
relpath (const char *can_fname, const char *can_reldir, char *buf, size_t len)
{
bool buf_err = false;
/* Skip the prefix common to --relative-to and path. */
int common_index = path_common_prefix (can_reldir, can_fname);
if (!common_index)
return false;
const char *relto_suffix = can_reldir + common_index;
const char *fname_suffix = can_fname + common_index;
/* Skip over extraneous '/'. */
if (*relto_suffix == '/')
relto_suffix++;
if (*fname_suffix == '/')
fname_suffix++;
/* Replace remaining components of --relative-to with '..', to get
to a common directory. Then output the remainder of fname. */
if (*relto_suffix)
{
buf_err |= buffer_or_output ("..", &buf, &len);
for (; *relto_suffix; ++relto_suffix)
{
if (*relto_suffix == '/')
buf_err |= buffer_or_output ("/..", &buf, &len);
}
if (*fname_suffix)
{
buf_err |= buffer_or_output ("/", &buf, &len);
buf_err |= buffer_or_output (fname_suffix, &buf, &len);
}
}
else
{
buf_err |= buffer_or_output (*fname_suffix ? fname_suffix : ".",
&buf, &len);
}
if (buf_err)
error (0, ENAMETOOLONG, "%s", _("generating relative path"));
return !buf_err;
}
您可以查看“relpath.c”的其余部分here。 “ln.c”中有一个更高级别的包装函数,它在调用relpath之前规范化其路径名参数,它被命名为convert_abs_rel()。这可能是你想要大部分时间打电话的。
使用cwalk你可以使用cwk_path_get_relative,它甚至可以跨平台工作:
#include <cwalk.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char buffer[FILENAME_MAX];
cwk_path_get_relative("/hello/there/", "/hello/world", buffer, sizeof(buffer));
printf("The relative path is: %s", buffer);
return EXIT_SUCCESS;
}
输出:
The relative path is: ../world