给定两个绝对路径,如何找到相对路径?

How to find relative path given two absolute paths?

给定两个绝对路径,例如

如何获得从一个到另一个的相对路径,../a

在某种意义上,与realpath相反。

使用第一条绝对路径构建一棵树,然后将第二条路径添加到该树,然后从一片叶子走到另一片叶子:从一个节点到其父节点的步骤被翻译为“../”序列,并且从节点到其子节点之一的步骤被翻译成该子节点的名称。请注意,可能有不止一种解决方案。例如:

1) /a/path/to/a

2) /a/path/to/a/new/one

从 (1) 到 (2) 的明显路径是 new/one../../../a/path/to/a/new/one 也是有效的。当你编写算法来在你的树中行走时,你必须意识到这一点

我在这里回答了一个类似的问题:

没有这方面的标准函数。为此,vi-like-emacs 中有一个函数。快速检查 apropos relative 显示了一些可能实现此功能的其他程序:例如 revpath)。

它可以作为字符串操作来完成(不需要计算工作目录):

  • 首先找到以路径分隔符结尾的最长公共前缀。
  • 如果没有共同的前缀,你就完成了
  • 从(...的副本)当前和目标字符串中去除公共前缀
  • 将当前字符串中的每个目录名替换为“..”
  • 在目标字符串前面添加(使用路径分隔符)
  • return 组合字符串

第二步中的“done”假设您要使用相对路径来缩短结果。另一方面,您可能希望使用相对路径名而不考虑长度。那样的话,跳过这一步就可以了(结果会更长,但是相对)。

找到最长的公共路径(在本例中为/a/path/to)并将其从两个绝对路径中删除。那将给出:

  • /a
  • /somewhere/else

现在,将起始路径中的每个路径组件替换为 ../,并将结果添加到目标路径中。如果你想从目录 else 转到目录 a,那会给你:

../../a

如果你想走另一条路,你应该:

../somewhere/else

这在 "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. There is a higher-level wrapper function in "ln.c" which canonicalizes its path name arguments before calling relpath, it is named convert_abs_rel() 的其余部分。这可能是您大多数时候想要调用的内容。

使用 cwalk you can use 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