12

realpath私が必要とすることはしますが、パス内のファイルが実際に存在する場合にのみ機能します。

ディレクトリ/ファイルが実際に存在するかどうかに関係なく、文字列から正規化されたパス (たとえば../some/./directory/a/b/c/../d、へ)を返す関数が必要ですsome/directory/a/b/d

PathCanonicalize基本的に、 Windowsと同等です。

そのような機能はすでに存在しますか?

4

6 に答える 6

10

これに利用できる標準ライブラリ関数はないと思います。

この関数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 ライセンス コードを再利用することが許容されることを前提としています。)

于 2015-05-17T08:59:57.370 に答える
8

あなたの問題の声明によると、以下はまさにあなたが求めていることを行います。コードの大部分は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
于 2015-05-17T08:48:51.690 に答える
5

別の試み。これの癖/機能:

  • ソース文字列に正規化されません。呼び出し元が提供したスペースに書き込みます
  • 絶対パスと相対パスの概念があります (ソース パスは '/' で始まりましたか?): すべてのソースを食べるのに十分な '..' が存在する場合、絶対パスの '/' と '.' を発行します。親戚のため
  • ソースパスの要素が実際のファイルシステムオブジェクトに対応するかどうかはわかりません
  • C99 可変長配列を使用し、呼び出し元が提供する空間に戻ると、malloc はありませんが、内部でいくつかのコピーを作成します。
  • これらのコピーを指定すると、ソースと宛先は同じになる可能性があります
  • strtok_r(3)を使用します。長さがゼロのトークンを返さないという癖は、隣接する「/」文字の望ましい動作と一致しているようです。

ソース:

#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 バイトを超えるターゲット ストレージを提供する可能性がありますが、それは難しい世界です。

于 2015-05-20T22:52:15.087 に答える
4

あなたのホストは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"でしょうか?

私が提案したことは、.あなたが望むかもしれないし、望まないかもしれないファイル名(またはディレクトリ名)の一部であることを文字に許可します。必要に応じて調整します。

于 2015-05-17T08:21:10.040 に答える
2

*nix (Linux など) を使用しているようです。

Q: コンパイラにcanonicalize_file_name()がありますか?

それ以外の場合、C++ でプログラミングしている場合は、Boost を検討することをお勧めします。

ブースト::ファイルシステム::カノニカル

于 2015-05-17T06:38:08.063 に答える