如何获取不存在的文件或目录的绝对路径?

19

如何在GNU/Linux上使用C/C++从给定的相对路径确定文件或目录的绝对路径?
我知道使用realpath()可以实现,但它无法处理不存在的文件。

假设用户输入../non-existant-directory/file.txt,程序工作目录为/home/user/
我需要一个函数返回/home/non-existant-directory/file.txt

我需要这个函数来检查给定路径是否在某个子目录中。


我认为没有类似的内置功能。你需要自己编写代码。 - Colin D
4个回答

13

尝试使用realpath。如果失败,请一次一个地从末尾删除路径组件并重试realpath,直到成功为止。然后将您删除的组件附加回成功的realpath调用的结果。

如果您确定包含目录存在并且只想在那里创建文件,则最多只需删除一个组件。

另一种方法是首先创建文件,然后调用realpath


如果文件名不存在的部分中有 ...,那么这将无法正常工作。例如 /foo/bar/this_dir_doesn't_exist/../../baz 应该被规范化为 /foo/baz,但是使用您的方法它将保持不变。我认为您需要先解析 ... - Timmmm
2
@Timmmm:在调用realpath之前(或重新实现它自己),您无法解决它们,因为您不知道..之前的组件是否是符号链接。如果可能存在,则只需要在尾部应用..组件,然后按照我的答案中的过程进行跟随即可。 - R.. GitHub STOP HELPING ICE
嗯,非常好的观点 - 所以,先执行你的方法,然后解决 ..。我希望Linux有一个系统调用来处理这个。 - Timmmm

1
我知道这是一个非常老的话题,但是如果有人在这里寻找关于realpath是否在操作数中的一个或多个路径组件丢失时工作的答案:
答案是,至少在coreutils v. 8+的大多数Linux变体上,如果您向其提供"-m"选项,它将工作。以下是相关的帮助文本:
$ realpath --help
Usage: realpath [OPTION]... FILE...
Print the resolved absolute file name;
all but the last component must exist

  -e, --canonicalize-existing  all components of the path must exist
  -m, --canonicalize-missing   no components of the path need exist
  -L, --logical                resolve '..' components before symlinks
  -P, --physical               resolve symlinks as encountered (default)
  -q, --quiet                  suppress most error messages
      --relative-to=FILE       print the resolved path relative to FILE
      --relative-base=FILE     print absolute paths unless paths below FILE
  -s, --strip, --no-symlinks   don't expand symlinks
  -z, --zero                   separate output with NUL rather than newline

      --help     display this help and exit
      --version  output version information and exit


这是一个在安装有coreutils v. 8.22的机器上的示例:
$ pwd
/home/alice/foo/bar
$ ls /home/alice/foo/bar/baz/quux/splat
ls: cannot access /home/alice/foo/bar/baz/quux/splat: No such file or directory
$ realpath -e ../../foo/bar/baz/quux/splat
realpath: ‘../../foo/bar/baz/quux/splat’: No such file or directory
$ realpath -m ../../foo/bar/baz/quux/splat
/home/alice/foo/bar/baz/quux/splat

谢谢你的回答,不过这个问题是关于C编程语言,而不是bash。 - undefined

0

你可以尝试像这样做:

let pbuf = std::path::PathBuf::from_str("somewhere/something.json").unwrap();
let parent = pbuf.parent().unwrap().canonicalize().unwrap_or(std::env::current_dir().unwrap());
let file = pbuf.file_name().unwrap().to_str().unwrap();
let final_path = parent.join(file).to_str().unwrap().to_owned();

如果父路径不存在(或由于任何原因失败),它将在其位置附加当前目录。

0

正如 GitHub 上的 @R.. 所指出的,您可以在 realpath() 上构建此功能。以下是一个示例函数,它使用 realpath() 来确定存在部分路径的规范形式,并将路径不存在的部分附加到其后。

由于 realpath() 操作 C 风格字符串,在这里我决定也使用它们。但是该函数可以轻松地重新编写为使用 std::string(只需不要忘记在将其复制到 std::string 后释放 canonical_file_path!)。

请注意,路径不存在的部分不会删除重复的"/"条目;它只是附加到存在部分路径的规范形式。

////////////////////////////////////////////////////////////////////////////////
// Return the input path in a canonical form. This is achieved by expanding all
// symbolic links, resolving references to "." and "..", and removing duplicate
// "/" characters.
//
// If the file exists, its path is canonicalized and returned. If the file,
// or parts of the containing directory, do not exist, path components are
// removed from the end until an existing path is found. The remainder of the
// path is then appended to the canonical form of the existing path,
// and returned. Consequently, the returned path may not exist. The portion
// of the path which exists, however, is represented in canonical form.
//
// If successful, this function returns a C-string, which needs to be freed by
// the caller using free().
//
// ARGUMENTS:
//   file_path
//   File path, whose canonical form to return.
//
// RETURNS:
//   On success, returns the canonical path to the file, which needs to be freed
//   by the caller.
//
//   On failure, returns NULL.
////////////////////////////////////////////////////////////////////////////////
char *make_file_name_canonical(char const *file_path)
{
  char *canonical_file_path  = NULL;
  unsigned int file_path_len = strlen(file_path);

  if (file_path_len > 0)
  {
    canonical_file_path = realpath(file_path, NULL);
    if (canonical_file_path == NULL && errno == ENOENT)
    {
      // The file was not found. Back up to a segment which exists,
      // and append the remainder of the path to it.
      char *file_path_copy = NULL;
      if (file_path[0] == '/'                ||
          (strncmp(file_path, "./", 2) == 0) ||
          (strncmp(file_path, "../", 3) == 0))
      {
        // Absolute path, or path starts with "./" or "../"
        file_path_copy = strdup(file_path);
      }
      else
      {
        // Relative path
        file_path_copy = (char*)malloc(strlen(file_path) + 3);
        strcpy(file_path_copy, "./");
        strcat(file_path_copy, file_path);
      }

      // Remove path components from the end, until an existing path is found
      for (int char_idx = strlen(file_path_copy) - 1;
           char_idx >= 0 && canonical_file_path == NULL;
           --char_idx)
      {
        if (file_path_copy[char_idx] == '/')
        {
          // Remove the slash character
          file_path_copy[char_idx] = '\0';

          canonical_file_path = realpath(file_path_copy, NULL);
          if (canonical_file_path != NULL)
          {
            // An existing path was found. Append the remainder of the path
            // to a canonical form of the existing path.
            char *combined_file_path = (char*)malloc(strlen(canonical_file_path) + strlen(file_path_copy + char_idx + 1) + 2);
            strcpy(combined_file_path, canonical_file_path);
            strcat(combined_file_path, "/");
            strcat(combined_file_path, file_path_copy + char_idx + 1);
            free(canonical_file_path);
            canonical_file_path = combined_file_path;
          }
          else
          {
            // The path segment does not exist. Replace the slash character
            // and keep trying by removing the previous path component.
            file_path_copy[char_idx] = '/';
          }
        }
      }

      free(file_path_copy);
    }
  }

  return canonical_file_path;
}

2
这对于路径中虚构部分中的多个 ../ 无效。我希望能够消除字符串中所有的 ./../,而不管路径是否存在。在 Windows 上使用 _fullpath() 可以实现此目的。我真的不理解 realpath() 在不存在的路径上失败的用例。您可以使用 stat() 检查路径。 - Rich Jahn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接