Path.Combine이 Path.DirectorySeparatorChar로 시작하는 파일 이름을 올바르게 연결하지 않는 이유는 무엇입니까?


185

로부터 직접 실행 창 Visual Studio에서 :

> Path.Combine(@"C:\x", "y")
"C:\\x\\y"
> Path.Combine(@"C:\x", @"\y")
"\\y"

둘 다 동일해야합니다.

이전 FileSystemObject.BuildPath ()가 이런 식으로 작동하지 않았습니다 ...



@ 조 바보 야! 또한 동등한 기능 이 Node.JS에서 잘 작동 한다는 것을 지적해야합니다 ... Microsoft에서 내 머리를 흔드는 ...
NH.

2
@zwcloud .NET Core / Standard의 경우 기본적 Path.Combine()으로 이전 버전과의 호환성 (기존 동작과의 호환성)을위한 것입니다. 당신은 더 나은 개시 될 것입니다 Path.Join(): "방법을 결합과는 달리,이 방법은 루트에 반환 된 경로를 시도하지 않습니다 가입 (경로 2는, 방법을 가입 절대 경로가 없습니다 폐기 경로 1과 리턴 경로 2 결합으로 않는 경우 즉,이다. 방법은 않습니다). "
Stajs

답변:


205

이것은 문서가 말하는 것과 정확히 일치하기 때문에 일종의 철학적 질문입니다 (아마도 Microsoft 만 진정으로 대답 할 수 있음).

System.IO.Path.Combine

"path2에 절대 경로가 포함 된 경우이 방법은 path2를 반환합니다."

다음 은 .NET 소스 의 실제 Combine 방법 입니다. 당신은 호출하는 것을 볼 수 있습니다 CombineNoChecks 다음 호출 IsPathRooted을 경로가 그렇다면 경로 2에 되돌아 :

public static String Combine(String path1, String path2) {
    if (path1==null || path2==null)
        throw new ArgumentNullException((path1==null) ? "path1" : "path2");
    Contract.EndContractBlock();
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);

    return CombineNoChecks(path1, path2);
}

internal static string CombineNoChecks(string path1, string path2)
{
    if (path2.Length == 0)
        return path1;

    if (path1.Length == 0)
        return path2;

    if (IsPathRooted(path2))
        return path2;

    char ch = path1[path1.Length - 1];
    if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar &&
            ch != VolumeSeparatorChar) 
        return path1 + DirectorySeparatorCharAsString + path2;
    return path1 + path2;
}

나는 이론적 근거가 무엇인지 모른다. 해결책은 두 번째 경로의 시작 부분에서 DirectorySeparatorChar를 제거하는 것입니다. 어쩌면 자신의 Combine 메소드를 작성하고 Path.Combine ()을 호출하십시오.


디스 어셈블 된 코드를 살펴보면 (내 게시물 확인) 올바른 방법입니다.
Gulzar Nazim

7
"현재 작업 디렉토리"알고리즘에 쉽게 액세스 할 수있는 방식으로 작동한다고 생각합니다.
BCS

cd (component)명령 줄에서 시퀀스를 수행하는 것처럼 작동하는 것 같습니다 . 나에게 합리적으로 들린다.
Adrian Ratnapala

11
이 트림을 사용하여 원하는 효과 문자열을 얻습니다. strFilePath = Path.Combine (basePath, otherPath.TrimStart (new char [] { '\\', '/'}));
Matthew Lock

3
나는 작업 코드를 Path.Combine안전하기 위해 변경 했지만 파산했습니다 .. 너무 바보입니다 :)
sotn

23

Path.Combine 메서드에 대한 .NET Reflector 에서 디스 어셈블 된 코드입니다 . IsPathRooted 기능을 확인하십시오. 두 번째 경로가 루트 인 경우 (DirectorySeparatorChar로 시작) 두 번째 경로를 그대로 리턴하십시오.

public static string Combine(string path1, string path2)
{
    if ((path1 == null) || (path2 == null))
    {
        throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
    }
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);
    if (path2.Length == 0)
    {
        return path1;
    }
    if (path1.Length == 0)
    {
        return path2;
    }
    if (IsPathRooted(path2))
    {
        return path2;
    }
    char ch = path1[path1.Length - 1];
    if (((ch != DirectorySeparatorChar) &&
         (ch != AltDirectorySeparatorChar)) &&
         (ch != VolumeSeparatorChar))
    {
        return (path1 + DirectorySeparatorChar + path2);
    }
    return (path1 + path2);
}


public static bool IsPathRooted(string path)
{
    if (path != null)
    {
        CheckInvalidPathChars(path);
        int length = path.Length;
        if (
              (
                  (length >= 1) &&
                  (
                      (path[0] == DirectorySeparatorChar) ||
                      (path[0] == AltDirectorySeparatorChar)
                  )
              )

              ||

              ((length >= 2) &&
              (path[1] == VolumeSeparatorChar))
           )
        {
            return true;
        }
    }
    return false;
}

23

이 문제를 해결하고 싶었습니다.

string sample1 = "configuration/config.xml";
string sample2 = "/configuration/config.xml";
string sample3 = "\\configuration/config.xml";

string dir1 = "c:\\temp";
string dir2 = "c:\\temp\\";
string dir3 = "c:\\temp/";

string path1 = PathCombine(dir1, sample1);
string path2 = PathCombine(dir1, sample2);
string path3 = PathCombine(dir1, sample3);

string path4 = PathCombine(dir2, sample1);
string path5 = PathCombine(dir2, sample2);
string path6 = PathCombine(dir2, sample3);

string path7 = PathCombine(dir3, sample1);
string path8 = PathCombine(dir3, sample2);
string path9 = PathCombine(dir3, sample3);

물론 모든 경로 1-9는 끝에 동등한 문자열을 포함해야합니다. 내가 생각해 낸 PathCombine 방법은 다음과 같습니다.

private string PathCombine(string path1, string path2)
{
    if (Path.IsPathRooted(path2))
    {
        path2 = path2.TrimStart(Path.DirectorySeparatorChar);
        path2 = path2.TrimStart(Path.AltDirectorySeparatorChar);
    }

    return Path.Combine(path1, path2);
}

또한이 문자열 처리를 수동으로 수행해야한다는 것이 상당히 성가신 것으로 생각되며, 그 이유에 관심이 있습니다.


19

제 생각에는 이것은 버그입니다. 문제는 두 가지 유형의 "절대"경로가 있다는 것입니다. "d : \ mydir \ myfile.txt"경로는 절대적이고 "\ mydir \ myfile.txt"경로는 드라이브 문자가 없어도 "절대"로 간주됩니다. 제 생각에 올바른 동작은 두 번째 경로가 디렉토리 구분 기호로 시작하고 UNC 경로가 아닌 경우 첫 번째 경로에서 드라이브 문자를 추가하는 것입니다. 필요한 경우 원하는 동작을 가진 자체 도우미 래퍼 함수를 ​​작성하는 것이 좋습니다.


7
사양과 일치하지만 예상했던 것과 다릅니다.
dthrasher

@Jake 버그 수정을 피하는 것은 아닙니다. 그것은 무언가를하는 방법에 대해 오랫동안 열심히 생각한 다음, 그들이 동의하는 것을 고수하는 사람들입니다. 또한 .Net 프레임 워크 (를 포함하는 라이브러리 Path.Combine)와 C # 언어 의 차이점에 유의하십시오 .
Grault

9

에서 MSDN :

지정된 경로 중 하나가 길이가 0 인 문자열 인 경우이 메서드는 다른 경로를 반환합니다. path2에 절대 경로가 포함 된 경우이 메서드는 path2를 반환합니다.

귀하의 예에서 path2는 절대적입니다.


7

" Path.Combine은 본질적으로 쓸모가 없습니다. " 라는 제목의 "Microsoft에 대한 증오"블로그에서 Christian Graus 의 조언에 따라 다음 은 내 해결책입니다.

public static class Pathy
{
    public static string Combine(string path1, string path2)
    {
        if (path1 == null) return path2
        else if (path2 == null) return path1
        else return path1.Trim().TrimEnd(System.IO.Path.DirectorySeparatorChar)
           + System.IO.Path.DirectorySeparatorChar
           + path2.Trim().TrimStart(System.IO.Path.DirectorySeparatorChar);
    }

    public static string Combine(string path1, string path2, string path3)
    {
        return Combine(Combine(path1, path2), path3);
    }
}

네임 스페이스 충돌해야한다는 일부 조언은 ... 나는 갔다 Pathy약간으로, 그리고와 네임 스페이스 충돌을 피하기 위해 System.IO.Path.

편집 : null 매개 변수 검사 추가


4

이 코드는 트릭을 수행해야합니다.

        string strFinalPath = string.Empty;
        string normalizedFirstPath = Path1.TrimEnd(new char[] { '\\' });
        string normalizedSecondPath = Path2.TrimStart(new char[] { '\\' });
        strFinalPath =  Path.Combine(normalizedFirstPath, normalizedSecondPath);
        return strFinalPath;

4

실제 세부 사항을 모르면 내 생각에 상대 URI에 참여하는 것처럼 참여하려고합니다. 예를 들면 다음과 같습니다.

urljoin('/some/abs/path', '../other') = '/some/abs/other'

이는 선행 슬래시가있는 경로를 결합 할 때 실제로는 한베이스를 다른베이스에 결합하는 것입니다.이 경우 두 번째가 우선합니다.


슬래시를 설명해야한다고 생각합니다. 또한 .NET과 어떤 관련이 있습니까?
Peter Mortensen

3

이유:

두 번째 URL은 절대 경로로 간주 Combine됩니다. 마지막 경로가 절대 경로 인 경우 메소드는 마지막 경로 만 반환합니다.

솔루션 :/ 두 번째 경로 의 시작 슬래시 ( /SecondPathto SecondPath)를 제거하십시오 . 그런 다음 예외로 작동합니다.


3

이것은 (상대) 경로가 일반적으로 어떻게 처리되는지를 고려할 때 실제로 어떤 의미가 있습니다.

string GetFullPath(string path)
{
     string baseDir = @"C:\Users\Foo.Bar";
     return Path.Combine(baseDir, path);
}

// Get full path for RELATIVE file path
GetFullPath("file.txt"); // = C:\Users\Foo.Bar\file.txt

// Get full path for ROOTED file path
GetFullPath(@"C:\Temp\file.txt"); // = C:\Temp\file.txt

진짜 질문은 :로 시작하는 경로가 "\""뿌리"로 간주 되는 이유는 무엇 입니까? 이것은 나에게도 새로운 것이었지만 Windows 에서는 그렇게 작동합니다 .

new FileInfo("\windows"); // FullName = C:\Windows, Exists = True
new FileInfo("windows"); // FullName = C:\Users\Foo.Bar\Windows, Exists = False

1

경로를 잃지 않고 두 경로를 결합하려면 다음을 사용할 수 있습니다.

?Path.Combine(@"C:\test", @"\test".Substring(0, 1) == @"\" ? @"\test".Substring(1, @"\test".Length - 1) : @"\test");

또는 변수가있는 경우 :

string Path1 = @"C:\Test";
string Path2 = @"\test";
string FullPath = Path.Combine(Path1, Path2.IsRooted() ? Path2.Substring(1, Path2.Length - 1) : Path2);

두 경우 모두 "C : \ test \ test"를 반환합니다.

먼저 Path2가 /로 시작하는지 평가하고 true이면 첫 번째 문자없이 Path2를 반환합니다. 그렇지 않으면 전체 Path2를 반환하십시오.


1
설명 할 수 있는 유일한 문자는 아니기 때문에 통화로 == @"\"수표 를 교체하는 것이 더 안전 할 것 입니다. Path.IsRooted()"\"
rumblefx0

0

이 두 가지 방법으로 구분 기호가있는 두 문자열을 실수로 결합하지 않아도됩니다.

    public static string Combine(string x, string y, char delimiter) {
        return $"{ x.TrimEnd(delimiter) }{ delimiter }{ y.TrimStart(delimiter) }";
    }

    public static string Combine(string[] xs, char delimiter) {
        if (xs.Length < 1) return string.Empty;
        if (xs.Length == 1) return xs[0];
        var x = Combine(xs[0], xs[1], delimiter);
        if (xs.Length == 2) return x;
        var ys = new List<string>();
        ys.Add(x);
        ys.AddRange(xs.Skip(2).ToList());
        return Combine(ys.ToArray(), delimiter);
    }

0

\는 "현재 드라이브의 루트 디렉토리"를 의미합니다. 귀하의 예에서 이것은 현재 드라이브의 루트 디렉토리에있는 "test"폴더를 의미합니다. 따라서 "c : \ test"와 같습니다.


0

Path.Combine의 두 번째 매개 변수 (path2)에서 시작 슬래시 ( '\')를 제거하십시오.


질문은 이것을 묻지 않습니다.
LarsTech

0

다음과 같이 집계 함수를 사용하여 경로를 강제로 결합했습니다.

public class MyPath    
{
    public static string ForceCombine(params string[] paths)
    {
        return paths.Aggregate((x, y) => Path.Combine(x, y.TrimStart('\\')));
    }
}

0

Ryan이 언급했듯이 문서에서 말한 것과 정확히 일치합니다.

DOS 시간부터 현재 디스크 및 현재 경로가 구별됩니다. \루트 경로이지만 CURRENT DISK의 경우입니다.

모든 " 디스크 " 마다 별도의 " 현재 경로 "가 있습니다. 를 사용하여 디스크를 cd D:변경하면 현재 경로를D:\ "D : \ whatever \ was \ the \ last \ path \ accessed \ on \ this \ disk"로 변경하십시오.

따라서 Windows에서 리터럴 @"\x"은 "CURRENTDISK : \ x"를 의미합니다. 따라서 Path.Combine(@"C:\x", @"\y")알려진 디스크에 있지는 않지만 상대 경로가 아닌 루트 경로를 두 번째 매개 변수로 사용합니다.«현재 디스크»일 수는 없으므로 python은을 반환합니다 "\\y".

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.