How to find relative path given two absolute paths?

后端 未结 5 1450
天命终不由人
天命终不由人 2020-12-19 08:31

Given two absolute paths, e.g.

  • /a/path/to/a
  • /a/path/to/somewhere/else

How can I get a relative path from one

相关标签:
5条回答
  • 2020-12-19 08:41

    This is implemented in the "ln" command, part of the GNU Coreutils package (for ln -r). Obviously there are many ways to go about this, and one could even derive some benefit from coming up with a solution oneself, without looking at existing code. Personally I find the code in Coreutils to be rather instructive.

    If I had to convert absolute paths to a relative path in a C project, I would just copy "relpath.c" from Coreutils. It has a few dependencies to other utility functions in the package, these would need to be brought in in some form as well. Here is the main "relpath()" function. Note that it works on canonicalized pathnames, for example it doesn't like for paths to contain stuff like "//" or "/.". Basically it finds the common prefix of the two paths, then it treats the four cases which arise depending on whether the remaining part of each path is empty or not.

    /* 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;
    }
    

    You can view the rest of "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(). That's probably what you want to be calling most of the time.

    0 讨论(0)
  • 2020-12-19 08:45

    Using cwalk you can use cwk_path_get_relative, which even works cross-platform:

    #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;
    }
    

    Output:

    The relative path is: ../world
    
    0 讨论(0)
  • 2020-12-19 08:50

    Find the longest common path (in this case, /a/path/to) and delete it from both absolute paths. That would give:

    • /a
    • /somewhere/else

    Now, replace each path component in the starting path with ../ and prepend the result to the destination path. If you want to go from directory else to directory a, that would give you:

    ../../a
    

    If you want to go the other way, you'd instead have:

    ../somewhere/else
    
    0 讨论(0)
  • 2020-12-19 08:53

    I answered a similar question here: Resolving a relative path without referencing the current directory on Windows.

    There is no standard function for this. There is a function in vi-like-emacs for this purpose. A quick check of apropos relative shows me few other programs which likely implement this: revpath for example).

    It could be done as a string-manipulation (no need to compute working directories):

    • start by finding the longest common prefix which ends with a path-separator.
    • if there is no common prefix, you are done
    • strip the common prefix from (a copy of...) the current and target strings
    • replace each directory-name in the current string with ".."
    • add that (with a path-separator) in front of the target string
    • return that combined string

    The "done" in the second step presumes that you want to use a relative path to shorten the result. On the other hand, you might want to use a relative pathname regardless of the length. In that case, just skip the step (the result will be longer, but relative).

    0 讨论(0)
  • 2020-12-19 08:56

    Build a tree with the first absolute path, then add the second path to that tree, and then walk from one leaf to the other: a step from one node to its parent is translated to a "../" sequence, and a step from a node to one of its children is translated to the name of that children. Notice that there might be more than one solution. For example:

    1) /a/path/to/a

    And

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

    The obvious path from (1) to (2) is new/one but ../../../a/path/to/a/new/one is also valid. When you write the algorithm to do the walking in your tree you have to be aware of this

    0 讨论(0)
提交回复
热议问题