替代 'realpath' 来解析路径中的“../”和“./”
Alternative to 'realpath' to resolve "../" and "./" in a path
realpath
做我需要的,但只有在路径中的文件实际存在时才有效。
我需要一个 returns 从字符串(例如 ../some/./directory/a/b/c/../d
到 some/directory/a/b/d
)的规范化路径的函数,无论 directories/files 是否实际存在
基本上等同于 windows 上的 PathCanonicalize
。
是否已经存在这样的功能?
听起来您使用的是 *nix(例如,Linux)。
问:你的编译器有canonicalize_file_name()吗?
否则,如果您使用 C++ 编程,您可能需要考虑 Boost:
Python源码有一个os.path.normpath for several platforms. The POSIX one (in the Lib/posixpath.py, for Python 3, line 318, or for Python 2的实现,第308行)不幸的是在Python,但是一般的逻辑可以很容易地用C重新实现(功能相当紧凑).经过多年使用测试。
Python 解释器和标准库源代码中还有其他平台规范路径实现,因此可移植解决方案可以是这些的组合。
可能其他 systems/libraries,用 C 编写,确实有相同的实现,因为 normpath 函数在安全意义上是至关重要的。
(拥有 Python 代码的主要优点是能够在 C 中测试您的函数,无论是并行输入,甚至是随机输入 - 这种测试对于确保函数安全很重要)
我假设您的主机是 windows 或 unix(都支持 ..
、.
和 /
,分别表示父目录、当前目录和目录分隔符).并且您的库提供对 posix 指定函数 getcwd()
的访问权限,该函数检索程序的当前工作目录(即,如果在文件名中没有指定路径的情况下打开,输出文件将被写入的完整路径).
首先调用 getcwd()
检索工作目录。如果其中的最后一个字符是 '/'
,请将该工作目录添加到您的输入字符串前,无需修改。否则将它和字符 '/'
添加到您的字符串中。
然后只处理字符串。找到字符串 "../"
的第一个实例并删除路径的前一部分和 "../"
。例如,如果字符串为 "/a/b/c/../foo"
,则结果将为 "/a/b/foo"
。重复直到字符串中没有 "../"
的实例。
唯一需要注意的是决定如何处理像 "/../"
这样的字符串(从技术上讲,这是一条不存在的路径)。将其保留为 "/"
(这样您总能得到一条可行的路径)或报告错误。
完成后,查找 "/./"
的实例并将其替换为 "/"
。这会将像 "/a/b/c/./"
这样的字符串变成 "/a/b/c/"
但会单独留下像 "/a/b/c./"
这样的字符串(它在 "/a/b"
中指定了一个名为 "c."
的目录)。
以上都是对字符串的处理。除了使用 getcwd()
之外,没有任何依赖于宿主环境的东西。所以无论路径是否实际存在,过程都是一样的。
一些花里胡哨的东西可能包括让它更好地与 windows 一起工作,例如将 '/'
和 '\'
视为等同的,以及处理像 [=34= 这样的驱动器说明符].
如果您不想调用 getcwd()
(例如,如果您的程序不依赖实际的工作目录,或者如果它有一个不存在的目录),那么您需要指定一个起始条件。例如,像 "../x/y/z"
这样的字符串会在哪里结束?
我的建议确实允许 .
字符成为文件名(或目录名)的一部分,您可能需要也可能不需要。根据需要进行调整。
根据您的问题陈述,以下内容完全符合您的要求。大部分代码来自评论中 link 中提供的 path.c
。添加了删除前面的 ../
的修改以符合您的问题陈述:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void pathCanonicalize (char *path);
int main (int argc, char **argv)
{
if (argc < 2) {
fprintf (stderr, "error: insufficient input, usage: %s <path>\n",
argv[0]);
return 1;
}
char *fullpath = strdup (argv[1]);
if (!fullpath) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
pathCanonicalize (fullpath);
printf ("\n original : %s\n canonical: %s\n\n", argv[1], fullpath);
free (fullpath);
return 0;
}
void pathCanonicalize (char *path)
{
size_t i;
size_t j;
size_t k;
//Move to the beginning of the string
i = 0;
k = 0;
//Replace backslashes with forward slashes
while (path[i] != '[=10=]') {
//Forward slash or backslash separator found?
if (path[i] == '/' || path[i] == '\') {
path[k++] = '/';
while (path[i] == '/' || path[i] == '\')
i++;
} else {
path[k++] = path[i++];
}
}
//Properly terminate the string with a NULL character
path[k] = '[=10=]';
//Move back to the beginning of the string
i = 0;
j = 0;
k = 0;
//Parse the entire string
do {
//Forward slash separator found?
if (path[i] == '/' || path[i] == '[=10=]') {
//"." element found?
if ((i - j) == 1 && !strncmp (path + j, ".", 1)) {
//Check whether the pathname is empty?
if (k == 0) {
if (path[i] == '[=10=]') {
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '[=10=]') {
path[k++] = '.';
path[k++] = '/';
}
} else if (k > 1) {
//Remove the final slash if necessary
if (path[i] == '[=10=]')
k--;
}
}
//".." element found?
else if ((i - j) == 2 && !strncmp (path + j, "..", 2)) {
//Check whether the pathname is empty?
if (k == 0) {
path[k++] = '.';
path[k++] = '.';
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
} else if (k > 1) {
//Search the path for the previous slash
for (j = 1; j < k; j++) {
if (path[k - j - 1] == '/')
break;
}
//Slash separator found?
if (j < k) {
if (!strncmp (path + k - j, "..", 2)) {
path[k++] = '.';
path[k++] = '.';
} else {
k = k - j - 1;
}
//Append a slash if necessary
if (k == 0 && path[0] == '/')
path[k++] = '/';
else if (path[i] == '/')
path[k++] = '/';
}
//No slash separator found?
else {
if (k == 3 && !strncmp (path, "..", 2)) {
path[k++] = '.';
path[k++] = '.';
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
} else if (path[i] == '[=10=]') {
k = 0;
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '[=10=]') {
k = 0;
path[k++] = '.';
path[k++] = '/';
} else {
k = 0;
}
}
}
} else {
//Copy directory name
memmove (path + k, path + j, i - j);
//Advance write pointer
k += i - j;
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
}
//Move to the next token
while (path[i] == '/')
i++;
j = i;
}
else if (k == 0) {
while (path[i] == '.' || path[i] == '/') {
j++,i++;
}
}
} while (path[i++] != '[=10=]');
//Properly terminate the string with a NULL character
path[k] = '[=10=]';
}
Use/Output
$ ./bin/pathcanonical ../some/./directory/a/b/c/../d
original : ../some/./directory/a/b/c/../d
canonical: some/directory/a/b/d
我认为没有任何标准库函数可用于此。
您可以在Apache httpd source code file server/util.c
. I believe it does exactly what you want: https://github.com/apache/httpd/blob/trunk/server/util.c#L500
中使用函数ap_getparents()
#ifdef WIN32
#define IS_SLASH(s) ((s == '/') || (s == '\'))
#else
#define IS_SLASH(s) (s == '/')
#endif
void ap_getparents(char *name)
{
char *next;
int l, w, first_dot;
/* Four paseses, as per RFC 1808 */
/* a) remove ./ path segments */
for (next = name; *next && (*next != '.'); next++) {
}
l = w = first_dot = next - name;
while (name[l] != '[=10=]') {
if (name[l] == '.' && IS_SLASH(name[l + 1])
&& (l == 0 || IS_SLASH(name[l - 1])))
l += 2;
else
name[w++] = name[l++];
}
/* b) remove trailing . path, segment */
if (w == 1 && name[0] == '.')
w--;
else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2]))
w--;
name[w] = '[=10=]';
/* c) remove all xx/../ segments. (including leading ../ and /../) */
l = first_dot;
while (name[l] != '[=10=]') {
if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2])
&& (l == 0 || IS_SLASH(name[l - 1]))) {
int m = l + 3, n;
l = l - 2;
if (l >= 0) {
while (l >= 0 && !IS_SLASH(name[l]))
l--;
l++;
}
else
l = 0;
n = l;
while ((name[n] = name[m]))
(++n, ++m);
}
else
++l;
}
/* d) remove trailing xx/.. segment. */
if (l == 2 && name[0] == '.' && name[1] == '.')
name[0] = '[=10=]';
else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.'
&& IS_SLASH(name[l - 3])) {
l = l - 4;
if (l >= 0) {
while (l >= 0 && !IS_SLASH(name[l]))
l--;
l++;
}
else
l = 0;
name[l] = '[=10=]';
}
}
(假设在您的项目中重新使用 Apache 许可代码是可以接受的。)
又一次尝试。 Quirks/features 这一个:
- 不规范化为源字符串;写入调用者提供的 space
- 具有绝对路径与相对路径的概念(源路径是否以“/”开头?):如果存在足够的“..”来吃掉所有源,则为绝对路径发出“/”,和一个“。”对于亲戚
- 不知道源路径中的元素是否对应于实际的文件系统对象
- 使用 C99 可变长度数组,并将它的 return 赋给调用者提供的 space,没有 malloc,但在幕后制作了几个副本。
- 鉴于这些副本,源和目标可以相同
- 使用 strtok_r(3),其不 return 零长度标记的怪癖似乎与相邻 '/' 字符的所需行为相匹配.
来源:
#include <stdlib.h>
#include <string.h>
int
pathcanon(const char *srcpath, char *dstpath, size_t sz)
{
size_t plen = strlen(srcpath) + 1, chk;
char wtmp[plen], *tokv[plen], *s, *tok, *sav;
int i, ti, relpath;
relpath = (*srcpath == '/') ? 0 : 1;
/* make a local copy of srcpath so strtok(3) won't mangle it */
ti = 0;
(void) strcpy(wtmp, srcpath);
tok = strtok_r(wtmp, "/", &sav);
while (tok != NULL) {
if (strcmp(tok, "..") == 0) {
if (ti > 0) {
ti--;
}
} else if (strcmp(tok, ".") != 0) {
tokv[ti++] = tok;
}
tok = strtok_r(NULL, "/", &sav);
}
chk = 0;
s = dstpath;
/*
* Construct canonicalized result, checking for room as we
* go. Running out of space leaves dstpath unusable: written
* to and *not* cleanly NUL-terminated.
*/
for (i = 0; i < ti; i++) {
size_t l = strlen(tokv[i]);
if (i > 0 || !relpath) {
if (++chk >= sz) return -1;
*s++ = '/';
}
chk += l;
if (chk >= sz) return -1;
strcpy(s, tokv[i]);
s += l;
}
if (s == dstpath) {
if (++chk >= sz) return -1;
*s++ = relpath ? '.' : '/';
}
*s = '[=10=]';
return 0;
}
编辑:当 s == dstpath 时错过了空间检查。合法调用者可能会提供超过 0 或 1 字节的目标存储空间,但这是一个艰难的世界。
realpath
做我需要的,但只有在路径中的文件实际存在时才有效。
我需要一个 returns 从字符串(例如 ../some/./directory/a/b/c/../d
到 some/directory/a/b/d
)的规范化路径的函数,无论 directories/files 是否实际存在
基本上等同于 windows 上的 PathCanonicalize
。
是否已经存在这样的功能?
听起来您使用的是 *nix(例如,Linux)。
问:你的编译器有canonicalize_file_name()吗?
否则,如果您使用 C++ 编程,您可能需要考虑 Boost:
Python源码有一个os.path.normpath for several platforms. The POSIX one (in the Lib/posixpath.py, for Python 3, line 318, or for Python 2的实现,第308行)不幸的是在Python,但是一般的逻辑可以很容易地用C重新实现(功能相当紧凑).经过多年使用测试。
Python 解释器和标准库源代码中还有其他平台规范路径实现,因此可移植解决方案可以是这些的组合。
可能其他 systems/libraries,用 C 编写,确实有相同的实现,因为 normpath 函数在安全意义上是至关重要的。
(拥有 Python 代码的主要优点是能够在 C 中测试您的函数,无论是并行输入,甚至是随机输入 - 这种测试对于确保函数安全很重要)
我假设您的主机是 windows 或 unix(都支持 ..
、.
和 /
,分别表示父目录、当前目录和目录分隔符).并且您的库提供对 posix 指定函数 getcwd()
的访问权限,该函数检索程序的当前工作目录(即,如果在文件名中没有指定路径的情况下打开,输出文件将被写入的完整路径).
首先调用 getcwd()
检索工作目录。如果其中的最后一个字符是 '/'
,请将该工作目录添加到您的输入字符串前,无需修改。否则将它和字符 '/'
添加到您的字符串中。
然后只处理字符串。找到字符串 "../"
的第一个实例并删除路径的前一部分和 "../"
。例如,如果字符串为 "/a/b/c/../foo"
,则结果将为 "/a/b/foo"
。重复直到字符串中没有 "../"
的实例。
唯一需要注意的是决定如何处理像 "/../"
这样的字符串(从技术上讲,这是一条不存在的路径)。将其保留为 "/"
(这样您总能得到一条可行的路径)或报告错误。
完成后,查找 "/./"
的实例并将其替换为 "/"
。这会将像 "/a/b/c/./"
这样的字符串变成 "/a/b/c/"
但会单独留下像 "/a/b/c./"
这样的字符串(它在 "/a/b"
中指定了一个名为 "c."
的目录)。
以上都是对字符串的处理。除了使用 getcwd()
之外,没有任何依赖于宿主环境的东西。所以无论路径是否实际存在,过程都是一样的。
一些花里胡哨的东西可能包括让它更好地与 windows 一起工作,例如将 '/'
和 '\'
视为等同的,以及处理像 [=34= 这样的驱动器说明符].
如果您不想调用 getcwd()
(例如,如果您的程序不依赖实际的工作目录,或者如果它有一个不存在的目录),那么您需要指定一个起始条件。例如,像 "../x/y/z"
这样的字符串会在哪里结束?
我的建议确实允许 .
字符成为文件名(或目录名)的一部分,您可能需要也可能不需要。根据需要进行调整。
根据您的问题陈述,以下内容完全符合您的要求。大部分代码来自评论中 link 中提供的 path.c
。添加了删除前面的 ../
的修改以符合您的问题陈述:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void pathCanonicalize (char *path);
int main (int argc, char **argv)
{
if (argc < 2) {
fprintf (stderr, "error: insufficient input, usage: %s <path>\n",
argv[0]);
return 1;
}
char *fullpath = strdup (argv[1]);
if (!fullpath) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
pathCanonicalize (fullpath);
printf ("\n original : %s\n canonical: %s\n\n", argv[1], fullpath);
free (fullpath);
return 0;
}
void pathCanonicalize (char *path)
{
size_t i;
size_t j;
size_t k;
//Move to the beginning of the string
i = 0;
k = 0;
//Replace backslashes with forward slashes
while (path[i] != '[=10=]') {
//Forward slash or backslash separator found?
if (path[i] == '/' || path[i] == '\') {
path[k++] = '/';
while (path[i] == '/' || path[i] == '\')
i++;
} else {
path[k++] = path[i++];
}
}
//Properly terminate the string with a NULL character
path[k] = '[=10=]';
//Move back to the beginning of the string
i = 0;
j = 0;
k = 0;
//Parse the entire string
do {
//Forward slash separator found?
if (path[i] == '/' || path[i] == '[=10=]') {
//"." element found?
if ((i - j) == 1 && !strncmp (path + j, ".", 1)) {
//Check whether the pathname is empty?
if (k == 0) {
if (path[i] == '[=10=]') {
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '[=10=]') {
path[k++] = '.';
path[k++] = '/';
}
} else if (k > 1) {
//Remove the final slash if necessary
if (path[i] == '[=10=]')
k--;
}
}
//".." element found?
else if ((i - j) == 2 && !strncmp (path + j, "..", 2)) {
//Check whether the pathname is empty?
if (k == 0) {
path[k++] = '.';
path[k++] = '.';
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
} else if (k > 1) {
//Search the path for the previous slash
for (j = 1; j < k; j++) {
if (path[k - j - 1] == '/')
break;
}
//Slash separator found?
if (j < k) {
if (!strncmp (path + k - j, "..", 2)) {
path[k++] = '.';
path[k++] = '.';
} else {
k = k - j - 1;
}
//Append a slash if necessary
if (k == 0 && path[0] == '/')
path[k++] = '/';
else if (path[i] == '/')
path[k++] = '/';
}
//No slash separator found?
else {
if (k == 3 && !strncmp (path, "..", 2)) {
path[k++] = '.';
path[k++] = '.';
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
} else if (path[i] == '[=10=]') {
k = 0;
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '[=10=]') {
k = 0;
path[k++] = '.';
path[k++] = '/';
} else {
k = 0;
}
}
}
} else {
//Copy directory name
memmove (path + k, path + j, i - j);
//Advance write pointer
k += i - j;
//Append a slash if necessary
if (path[i] == '/')
path[k++] = '/';
}
//Move to the next token
while (path[i] == '/')
i++;
j = i;
}
else if (k == 0) {
while (path[i] == '.' || path[i] == '/') {
j++,i++;
}
}
} while (path[i++] != '[=10=]');
//Properly terminate the string with a NULL character
path[k] = '[=10=]';
}
Use/Output
$ ./bin/pathcanonical ../some/./directory/a/b/c/../d
original : ../some/./directory/a/b/c/../d
canonical: some/directory/a/b/d
我认为没有任何标准库函数可用于此。
您可以在Apache httpd source code file server/util.c
. I believe it does exactly what you want: https://github.com/apache/httpd/blob/trunk/server/util.c#L500
ap_getparents()
#ifdef WIN32
#define IS_SLASH(s) ((s == '/') || (s == '\'))
#else
#define IS_SLASH(s) (s == '/')
#endif
void ap_getparents(char *name)
{
char *next;
int l, w, first_dot;
/* Four paseses, as per RFC 1808 */
/* a) remove ./ path segments */
for (next = name; *next && (*next != '.'); next++) {
}
l = w = first_dot = next - name;
while (name[l] != '[=10=]') {
if (name[l] == '.' && IS_SLASH(name[l + 1])
&& (l == 0 || IS_SLASH(name[l - 1])))
l += 2;
else
name[w++] = name[l++];
}
/* b) remove trailing . path, segment */
if (w == 1 && name[0] == '.')
w--;
else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2]))
w--;
name[w] = '[=10=]';
/* c) remove all xx/../ segments. (including leading ../ and /../) */
l = first_dot;
while (name[l] != '[=10=]') {
if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2])
&& (l == 0 || IS_SLASH(name[l - 1]))) {
int m = l + 3, n;
l = l - 2;
if (l >= 0) {
while (l >= 0 && !IS_SLASH(name[l]))
l--;
l++;
}
else
l = 0;
n = l;
while ((name[n] = name[m]))
(++n, ++m);
}
else
++l;
}
/* d) remove trailing xx/.. segment. */
if (l == 2 && name[0] == '.' && name[1] == '.')
name[0] = '[=10=]';
else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.'
&& IS_SLASH(name[l - 3])) {
l = l - 4;
if (l >= 0) {
while (l >= 0 && !IS_SLASH(name[l]))
l--;
l++;
}
else
l = 0;
name[l] = '[=10=]';
}
}
(假设在您的项目中重新使用 Apache 许可代码是可以接受的。)
又一次尝试。 Quirks/features 这一个:
- 不规范化为源字符串;写入调用者提供的 space
- 具有绝对路径与相对路径的概念(源路径是否以“/”开头?):如果存在足够的“..”来吃掉所有源,则为绝对路径发出“/”,和一个“。”对于亲戚
- 不知道源路径中的元素是否对应于实际的文件系统对象
- 使用 C99 可变长度数组,并将它的 return 赋给调用者提供的 space,没有 malloc,但在幕后制作了几个副本。
- 鉴于这些副本,源和目标可以相同
- 使用 strtok_r(3),其不 return 零长度标记的怪癖似乎与相邻 '/' 字符的所需行为相匹配.
来源:
#include <stdlib.h>
#include <string.h>
int
pathcanon(const char *srcpath, char *dstpath, size_t sz)
{
size_t plen = strlen(srcpath) + 1, chk;
char wtmp[plen], *tokv[plen], *s, *tok, *sav;
int i, ti, relpath;
relpath = (*srcpath == '/') ? 0 : 1;
/* make a local copy of srcpath so strtok(3) won't mangle it */
ti = 0;
(void) strcpy(wtmp, srcpath);
tok = strtok_r(wtmp, "/", &sav);
while (tok != NULL) {
if (strcmp(tok, "..") == 0) {
if (ti > 0) {
ti--;
}
} else if (strcmp(tok, ".") != 0) {
tokv[ti++] = tok;
}
tok = strtok_r(NULL, "/", &sav);
}
chk = 0;
s = dstpath;
/*
* Construct canonicalized result, checking for room as we
* go. Running out of space leaves dstpath unusable: written
* to and *not* cleanly NUL-terminated.
*/
for (i = 0; i < ti; i++) {
size_t l = strlen(tokv[i]);
if (i > 0 || !relpath) {
if (++chk >= sz) return -1;
*s++ = '/';
}
chk += l;
if (chk >= sz) return -1;
strcpy(s, tokv[i]);
s += l;
}
if (s == dstpath) {
if (++chk >= sz) return -1;
*s++ = relpath ? '.' : '/';
}
*s = '[=10=]';
return 0;
}
编辑:当 s == dstpath 时错过了空间检查。合法调用者可能会提供超过 0 或 1 字节的目标存储空间,但这是一个艰难的世界。