Получение пути относительно текущего рабочего каталога?
Я пишу консольную утилиту для обработки некоторых файлов, указанных в командной строке, но у меня возникла проблема, которую я не могу решить с помощью Google/Stack Overflow. Если указан полный путь, включая букву диска, как переформатировать этот путь относительно текущего рабочего каталога?
Должно быть что-то похожее на функцию VirtualPathUtility.MakeRelative, но если есть, это ускользает от меня.
Ответы
Ответ 1
Если вы не против переключения косых черт, вы можете использовать [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));
}
Ответ 2
Вы можете использовать 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));
}
}
}
Я не говорю, что это самая надежная вещь в мире (символические ссылки, вероятно, могут смутить ее), но это, вероятно, хорошо, если это всего лишь инструмент, который вы будете использовать иногда.
Ответ 3
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"
Ответ 4
Благодаря другим ответам здесь и после некоторых экспериментов я создал несколько очень полезных методов расширения:
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
не нормализует конечную косую черту. Он выводит любой путь, который использовался в конструкторе. Этот метод расширения позаботится об этом для вас.
Ответ 5
Существует также способ сделать это с некоторыми ограничениями. Это код из статьи:
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