Получение пути относительно текущего рабочего каталога?

Я пишу консольную утилиту для обработки файлов, указанных в командной строке, но я столкнулся с проблемой, которую не могу решить с помощью Google / Stack Overflow. Если указан полный путь, включая букву диска, как мне переформатировать этот путь относительно текущего рабочего каталога?

Должно быть что-то похожее на функцию VirtualPathUtility.MakeRelative, но если есть, то это ускользает от меня.


person Community    schedule 31.03.2009    source источник
comment
NDepend.Path - фантастическая библиотека, которую я начал использовать для всего, что связано с манипуляциями с путями: github.com/psmacchia/NDepend .Path   -  person Mike Marynowski    schedule 06.11.2017


Ответы (5)


Если вы не возражаете против переключения слэшей, вы можете [ab] использовать Uri:

Uri file = new Uri(@"c:\foo\bar\blop\blap.txt");
// Must end in a slash to indicate folder
Uri folder = new Uri(@"c:\foo\bar\");
string relativePath = 
Uri.UnescapeDataString(
    folder.MakeRelativeUri(file)
        .ToString()
        .Replace('/', Path.DirectorySeparatorChar)
    );

Как функция / метод:

string GetRelativePath(string filespec, string folder)
{
    Uri pathUri = new Uri(filespec);
    // Folders must end in a slash
    if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
    {
        folder += Path.DirectorySeparatorChar;
    }
    Uri folderUri = new Uri(folder);
    return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
person Marc Gravell    schedule 31.03.2009
comment
Хороший! и просто добавление .Replace ('/', '\\') делает все идеально - person jturcotte; 02.04.2009
comment
@total: лучше использовать .Replace ('/', Path.DirectorySeparatorChar) - person Ian Kemp; 19.08.2011
comment
Пробелы на вашем пути станут% 20, вам также придется их заменить. Возможно, лучше всего использовать Uri.UnescapeDataString. Также нельзя опускать последнюю обратную косую черту uri2. - person Kay Zed; 24.10.2011
comment
Также можно использовать Environment.CurrentDirectory для текущего каталога. - person Gaʀʀʏ; 17.07.2012
comment
Я обновил ответ, чтобы отразить пункты выше - person joshcomley; 24.01.2013
comment
См. Также stackoverflow.com/questions/275689/ - person CAD bloke; 05.06.2013
comment
+1 за очень хороший пример. Приносим извинения за редактирование, но я подумал, что инкапсулированный пример может быть более популярным / многоразовым. - person Gone Coding; 12.09.2013
comment
Спасибо за полезную функцию. Я добавил следующую строку в начале, чтобы учесть спецификации относительной папки: if (! Path.IsPathRooted (folder)) folder = Path.GetFullPath (folder); - person Ivan Krivyakov; 25.06.2014
comment
Это не касается всех крайних случаев. См. этот ответ. - person Muhammad Rehan Saeed; 20.08.2015
comment
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) можно упростить до if (folder[folder.Length - 1] != Path.DirectorySeparatorChar). - person RenniePet; 12.03.2018
comment
Предупреждение: использование этого в dotnet Core для Linux вызовет исключение. github.com/aspnet/dnx/pull/1691 - person scegg; 16.05.2019
comment
Внимание: не работает должным образом, если filespec == folder. GetRelativePath("c:\temp", "c:\temp") вернет "..\temp". .NET Framework 4.7.2 имеет / .NET Standard 2.1, наконец, будет иметь это как метод Framework: Path.GetRelativePath - person UweB; 13.06.2019
comment
Внимание! При таком подходе у меня возникли проблемы с файлом SchΣfer-Zimmermann2006_Chapter_RecurrentNeuralNetworksAreUniv.pdf. Он сгенерировал относительный путь, содержащий Schäfer-Zimmermann2006_Chapter_RecurrentNeuralNetworksAreUniv.pdf - person Ömer Cinbat; 07.10.2020

Вы можете использовать Environment.CurrentDirectory, чтобы получить текущий каталог, и FileSystemInfo.FullPath, чтобы получить полный путь к любому месту. Итак, полностью определите как текущий каталог, так и рассматриваемый файл, а затем проверьте, начинается ли полное имя файла с имени каталога - если это так, просто возьмите соответствующую подстроку на основе длины имени каталога.

Вот пример кода:

using System;
using System.IO;

class Program
{
    public static void Main(string[] args)
    {
        string currentDir = Environment.CurrentDirectory;
        DirectoryInfo directory = new DirectoryInfo(currentDir);
        FileInfo file = new FileInfo(args[0]);

        string fullDirectory = directory.FullName;
        string fullFile = file.FullName;

        if (!fullFile.StartsWith(fullDirectory))
        {
            Console.WriteLine("Unable to make relative path");
        }
        else
        {
            // The +1 is to avoid the directory separator
            Console.WriteLine("Relative path: {0}",
                              fullFile.Substring(fullDirectory.Length+1));
        }
    }
}

Я не говорю, что это самая надежная вещь в мире (символические ссылки, вероятно, могут ее запутать), но это нормально, если это просто инструмент, который вы будете использовать время от времени.

person Jon Skeet    schedule 31.03.2009
comment
Что ж, он не использует относительные пути ниже текущего каталога. Думаю, это будет весело. - person Ray; 01.04.2009
comment
Не уверен, что вы имеете в виду: c: \ Documents and Settings \ Jon Skeet \ Test ›test.exe c: \ Documents and Settings \ Jon Skeet \ Test \ foo \ bar Относительный путь: foo \ bar - person Jon Skeet; 01.04.2009
comment
Другими словами, у меня это работает. Приведите конкретный пример того, что вы ожидаете потерпеть неудачу. - person Jon Skeet; 01.04.2009
comment
Я имею в виду, что получение относительного пути для C: \ TestDir \ OneDirectory из C: \ TestDir \ AnotherDiectory не вернет .. \ OneDirectory. Я не говорю, что это нельзя было изменить, просто это будет непросто. - person Ray; 01.04.2009
comment
Ах, вы имеете в виду пути, которые находятся выше текущего каталога. Нет, это будет больно. Зачем вообще нужен относительный путь? - person Jon Skeet; 01.04.2009
comment
Рэй: если вам нужен относительный путь, который также может идти «вверх», вам нужно найти функцию Win32 PathRelativePathTo. - person Kay Zed; 24.10.2011
comment
Привет, Джон, как я могу заставить его работать как с каталогом, так и с файлом? - person Louis Rhys; 25.10.2011
comment
@LouisRhys: Непонятно, что вы имеете в виду или что именно вы пытаетесь сделать. Возможно, стоит задать новый вопрос (имея в виду этот и показать, как ответы не решают его), а не продолжать его в комментариях. - person Jon Skeet; 25.10.2011
comment
@JonSkeet Я имею в виду, что вы использовали FileInfo file = new FileInfo(args[0]), что означает, что он специфичен для файла. Интересно, можно ли сделать то же самое, когда args [0] может быть файлом или каталогом. Я не задавал нового вопроса, потому что это в основном тот же вопрос, что и этот. - person Louis Rhys; 25.10.2011
comment
@LouisRhys: Ну, вы, вероятно, могли бы использовать File.Exists и Directory.Exists, а затем создать либо FileInfo, либо DirectoryInfo. Я не знаю, что произойдет, если вы попытаетесь создать FileInfo для каталога. - person Jon Skeet; 25.10.2011
comment
Здесь задают вопрос - person Coops; 25.10.2011
comment
@JonSkeet Я тоже не знаю, и я не знаю, что произойдет с File.Exists (aDirectory) или Directory.Exists (файл). - person Louis Rhys; 25.10.2011
comment
@LouisRhys: А ты пробовал? - person Jon Skeet; 25.10.2011
comment
Я это сделал, и в моих случаях существующие методы возвращают false, и FileInfo.FullPath работает, даже если это каталог. Но я думаю, что мы не можем рассчитывать на это, если не задокументировано, что он всегда будет так себя вести (особенно с использованием FileInfo для каталога и наоборот). - person Louis Rhys; 25.10.2011

public string MakeRelativePath(string workingDirectory, string fullPath)
{
    string result = string.Empty;
    int offset;

    // this is the easy case.  The file is inside of the working directory.
    if( fullPath.StartsWith(workingDirectory) )
    {
        return fullPath.Substring(workingDirectory.Length + 1);
    }

    // the hard case has to back out of the working directory
    string[] baseDirs = workingDirectory.Split(new char[] { ':', '\\', '/' });
    string[] fileDirs = fullPath.Split(new char[] { ':', '\\', '/' });

    // if we failed to split (empty strings?) or the drive letter does not match
    if( baseDirs.Length <= 0 || fileDirs.Length <= 0 || baseDirs[0] != fileDirs[0] )
    {
        // can't create a relative path between separate harddrives/partitions.
        return fullPath;
    }

    // skip all leading directories that match
    for (offset = 1; offset < baseDirs.Length; offset++)
    {
        if (baseDirs[offset] != fileDirs[offset])
            break;
    }

    // back out of the working directory
    for (int i = 0; i < (baseDirs.Length - offset); i++)
    {
        result += "..\\";
    }

    // step into the file path
    for (int i = offset; i < fileDirs.Length-1; i++)
    {
        result += fileDirs[i] + "\\";
    }

    // append the file
    result += fileDirs[fileDirs.Length - 1];

    return result;
}

Этот код, вероятно, не является пуленепробиваемым, но это то, что я придумал. Он немного прочнее. Он принимает два пути и возвращает путь B относительно пути A.

пример:

MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\junk\\readme.txt")
//returns: "..\\..\\junk\\readme.txt"

MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\foo\\bar\\docs\\readme.txt")
//returns: "docs\\readme.txt"
person Ben    schedule 18.10.2013
comment
Я знаю, что это было давно. Но я нашел небольшую ошибку. В последнем цикле for вы используете fileDirs[offset] это должно быть fileDirs[i] - person Roy T.; 18.08.2014
comment
Я предпочитаю этот ответ, потому что он обрабатывает относительные каталоги "..", которые нужны быстро. Не уверен, что комментарий Роя все еще актуален; он работает у меня так, как он отображается в настоящее время. - person Ruud van Gaal; 17.01.2018

Благодаря другим ответам здесь и после некоторых экспериментов я создал несколько очень полезных методов расширения:

public static string GetRelativePathFrom(this FileSystemInfo to, FileSystemInfo from)
{
    return from.GetRelativePathTo(to);
}

public static string GetRelativePathTo(this FileSystemInfo from, FileSystemInfo to)
{
    Func<FileSystemInfo, string> getPath = fsi =>
    {
        var d = fsi as DirectoryInfo;
        return d == null ? fsi.FullName : d.FullName.TrimEnd('\\') + "\\";
    };

    var fromPath = getPath(from);
    var toPath = getPath(to);

    var fromUri = new Uri(fromPath);
    var toUri = new Uri(toPath);

    var relativeUri = fromUri.MakeRelativeUri(toUri);
    var relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    return relativePath.Replace('/', Path.DirectorySeparatorChar);
}

Важные точки:

  • Используйте FileInfo и DirectoryInfo в качестве параметров метода, чтобы не было двусмысленности относительно того, с чем работаете. Uri.MakeRelativeUri ожидает, что каталоги заканчиваются косой чертой.
  • DirectoryInfo.FullName не нормализует завершающую косую черту. Он выводит любой путь, использованный в конструкторе. Этот метод расширения позаботится об этом за вас.
person Ronnie Overby    schedule 16.05.2014

Также существует способ сделать это. с некоторыми ограничениями. Это код из статьи:

public string RelativePath(string absPath, string relTo)
    {
        string[] absDirs = absPath.Split('\\');
        string[] relDirs = relTo.Split('\\');
        // Get the shortest of the two paths 
        int len = absDirs.Length < relDirs.Length ? absDirs.Length : relDirs.Length;
        // Use to determine where in the loop we exited 
        int lastCommonRoot = -1; int index;
        // Find common root 
        for (index = 0; index < len; index++)
        {
            if (absDirs[index] == relDirs[index])
                lastCommonRoot = index;
            else break;
        }
        // If we didn't find a common prefix then throw 
        if (lastCommonRoot == -1)
        {
            throw new ArgumentException("Paths do not have a common base");
        }
        // Build up the relative path 
        StringBuilder relativePath = new StringBuilder();
        // Add on the .. 
        for (index = lastCommonRoot + 1; index < absDirs.Length; index++)
        {
            if (absDirs[index].Length > 0) relativePath.Append("..\\");
        }
        // Add on the folders 
        for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
        {
            relativePath.Append(relDirs[index] + "\\");
        }
        relativePath.Append(relDirs[relDirs.Length - 1]);
        return relativePath.ToString();
    }

При выполнении этого фрагмента кода:

string path1 = @"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir1"; 
string path2 = @"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir2\SubDirIWant";

System.Console.WriteLine (RelativePath(path1, path2));
System.Console.WriteLine (RelativePath(path2, path1));

он распечатывает:

..\SubDir2\SubDirIWant
..\..\SubDir1
person Dmitry Pavlov    schedule 23.05.2012