realpath
私が必要とすることはしますが、パス内のファイルが実際に存在する場合にのみ機能します。
ディレクトリ/ファイルが実際に存在するかどうかに関係なく、文字列から正規化されたパス (たとえば../some/./directory/a/b/c/../d
、へ)を返す関数が必要ですsome/directory/a/b/d
PathCanonicalize
基本的に、 Windowsと同等です。
そのような機能はすでに存在しますか?
realpath
私が必要とすることはしますが、パス内のファイルが実際に存在する場合にのみ機能します。
ディレクトリ/ファイルが実際に存在するかどうかに関係なく、文字列から正規化されたパス (たとえば../some/./directory/a/b/c/../d
、へ)を返す関数が必要ですsome/directory/a/b/d
PathCanonicalize
基本的に、 Windowsと同等です。
そのような機能はすでに存在しますか?
これに利用できる標準ライブラリ関数はないと思います。
この関数ap_getparents()
は、Apache httpdソース コード ファイルで使用できますserver/util.c
。私はそれがまさにあなたが望むことをすると信じています: https://github.com/apache/httpd/blob/trunk/server/util.c#L500
#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 ライセンス コードを再利用することが許容されることを前提としています。)
あなたの問題の声明によると、以下はまさにあなたが求めていることを行います。コードの大部分は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 指定の関数へのアクセスを提供します。.
/
getcwd()
getcwd()
作業ディレクトリを取得するための最初の呼び出し。その最後の文字が の場合は、'/'
その作業ディレクトリを変更せずに入力文字列に追加します。それ以外の場合は、それと文字の両方'/'
を文字列に追加します。
次に、文字列を処理します。文字列の最初のインスタンスを見つけて"../"
、パスの前の部分と"../"
. たとえば、文字列が"/a/b/c/../foo"
の場合、結果は になります"/a/b/foo"
。文字列にのインスタンスがなくなるまで繰り返し"../"
ます。
"/../"
唯一の注意点は、文字列(技術的には存在できないパス)をどうするかを決めることです。それをそのままにしておくか"/"
(常に実行可能なパスを取得するため)、エラーを報告してください。
それが完了したら、 のインスタンスを探して に"/./"
置き換えます"/"
。これにより、文字列 like が"/a/b/c/./"
に変換されます"/a/b/c/"
が、文字列 like (内で名前が"/a/b/c./"
付けられたディレクトリを指定する) だけが残ります。"c."
"/a/b"
上記はすべて、文字列を処理するだけです。の使用を除けばgetcwd()
、ホスト環境に依存するものは何もありません。したがって、パスが実際に存在するかどうかに関係なく、プロセスは同じになります。
'/'
と'\'
を同等のものとして扱い、 のようなドライブ指定子に対処するなど、Windows での動作を改善するなど、いくつかの追加機能が含まれる場合があります"a:"
。
呼び出したくない場合getcwd()
(たとえば、プログラムが実際に作業ディレクトリを持っていることに依存していない場合、または存在しないディレクトリがある場合)、開始条件を指定する必要があります。たとえば、次のような文字列はどこに行き着くの"../x/y/z"
でしょうか?
私が提案したことは、.
あなたが望むかもしれないし、望まないかもしれないファイル名(またはディレクトリ名)の一部であることを文字に許可します。必要に応じて調整します。
*nix (Linux など) を使用しているようです。
Q: コンパイラにcanonicalize_file_name()がありますか?
それ以外の場合、C++ でプログラミングしている場合は、Boost を検討することをお勧めします。