realpath
做我需要的,但只有在路径中的文件实际存在时才有效。
我需要一个从字符串(例如../some/./directory/a/b/c/../d
to some/directory/a/b/d
)返回规范化路径的函数,无论目录/文件是否实际存在
本质上相当于PathCanonicalize
在windows上。
这样的功能是否已经存在?
realpath
做我需要的,但只有在路径中的文件实际存在时才有效。
我需要一个从字符串(例如../some/./directory/a/b/c/../d
to some/directory/a/b/d
)返回规范化路径的函数,无论目录/文件是否实际存在
本质上相当于PathCanonicalize
在windows上。
这样的功能是否已经存在?
我认为没有任何标准库函数可用于此。
您可以使用Apache httpd源代码文件ap_getparents()
中的函数。我相信它完全符合您的要求:https ://github.com/apache/httpd/blob/trunk/server/util.c#L500server/util.c
#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] != '\0') {
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] = '\0';
/* c) remove all xx/../ segments. (including leading ../ and /../) */
l = first_dot;
while (name[l] != '\0') {
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] = '\0';
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] = '\0';
}
}
(这是假设在您的项目中重复使用 Apache 许可代码是可以接受的。)
Python 源代码具有适用于多个平台的os.path.normpath实现。不幸的是, POSIX 之一(在Lib/posixpath.py中,对于 Python 3,第 318 行,或对于 Python 2,第 308 行)在 Python 中,但一般逻辑可以很容易地在 C 中重新实现(函数非常紧凑)。经过多年使用的考验。
Python 解释器和标准库源代码中还有其他平台规范路径实现,因此可移植解决方案可以是这些的组合。
可能其他用 C 编写的系统/库确实具有相同的实现,因为 normpath 函数在安全意义上是至关重要的。
(并且拥有 Python 代码的主要优点是能够在 C 中使用任何(甚至是随机)并行输入来测试您的函数——这种测试对于确保函数安全很重要)
根据您的问题陈述,以下内容完全符合您的要求。大部分代码来自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] != '\0') {
//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] = '\0';
//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] == '\0') {
//"." element found?
if ((i - j) == 1 && !strncmp (path + j, ".", 1)) {
//Check whether the pathname is empty?
if (k == 0) {
if (path[i] == '\0') {
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '\0') {
path[k++] = '.';
path[k++] = '/';
}
} else if (k > 1) {
//Remove the final slash if necessary
if (path[i] == '\0')
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] == '\0') {
k = 0;
path[k++] = '.';
} else if (path[i] == '/' && path[i + 1] == '\0') {
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++] != '\0');
//Properly terminate the string with a NULL character
path[k] = '\0';
}
使用/输出
$ ./bin/pathcanonical ../some/./directory/a/b/c/../d
original : ../some/./directory/a/b/c/../d
canonical: some/directory/a/b/d
又一次尝试。这个的怪癖/特点:
来源:
#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 = '\0';
return 0;
}
编辑:当 s == dstpath 时错过了房间检查。合法调用者可能会提供超过 0 或 1 个字节的目标存储,但这是一个艰难的世界。
我假设您的主机是 windows 或 unix(都支持..
,.
和/
分别表示父目录、当前目录和目录分隔符)。并且您的库提供对 posix-specified 函数的访问,该函数getcwd()
检索程序的当前工作目录(即,如果在文件名中没有指定路径的情况下打开输出文件的完整路径)。
第一次调用getcwd()
以检索工作目录。如果其中的最后一个字符是 a '/'
,则将该工作目录添加到您的输入字符串之前而不进行修改。否则将它和字符都'/'
添加到您的字符串中。
然后只处理字符串。找到字符串的第一个实例"../"
并删除路径的前一部分和"../"
. 例如,如果字符串是"/a/b/c/../foo"
,结果将是"/a/b/foo"
. 重复直到"../"
字符串中没有 的实例。
唯一需要注意的是决定如何处理类似的字符串"/../"
(从技术上讲,这是一条不存在的路径)。要么保留它"/"
(这样你总能得到一条可行的路径)或报告错误。
完成后,查找 的实例"/./"
并将其替换为"/"
. 这会将字符串 like"/a/b/c/./"
转换为,但会单独"/a/b/c/"
留下字符串 like "/a/b/c./"
(指定名为"c."
within的目录)。"/a/b"
以上所有只是处理字符串。除了使用 之外getcwd()
,没有任何东西依赖于宿主环境。因此,无论路径是否实际存在,该过程都是相同的。
一些花里胡哨的东西可能包括使它在 Windows 上更好地工作,例如将'/'
和'\'
视为等效项,以及处理诸如"a:"
.
如果您不想调用getcwd()
(例如,如果您的程序不依赖于实际有一个工作目录,或者如果它有一个不存在的目录),那么您将需要指定一个起始条件。例如,像这样的字符串会在哪里"../x/y/z"
结束?
我所建议的确实允许该.
字符成为您可能想要或可能不想要的文件名(或目录名)的一部分。根据需要进行调整。