web-dev-qa-db-fra.com

Obtenir le chemin relatif au répertoire de travail actuel?

J'écris un utilitaire de console pour effectuer certains traitements sur les fichiers spécifiés sur la ligne de commande, mais je rencontre un problème que je ne peux pas résoudre avec Google/Stack Overflow. Si un chemin complet, y compris la lettre du lecteur, est spécifié, comment puis-je reformater ce chemin pour qu'il soit relatif au répertoire de travail en cours?

Il doit y avoir quelque chose de similaire à la fonction VirtualPathUtility.MakeRelative, mais si c'est le cas, cela m'échappe.

72
Amy

Si cela ne vous dérange pas que les barres obliques soient remplacées, vous pouvez [ab] utiliser 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)
    );

En fonction/méthode:

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));
}
128
Marc Gravell

Vous pouvez utiliser Environment.CurrentDirectory pour obtenir le répertoire actuel et FileSystemInfo.FullPath pour obtenir le chemin complet vers n'importe quel emplacement. Alors, qualifiez complètement le répertoire en cours et le fichier en question, puis vérifiez si le nom de fichier complet commence par le nom du répertoire. Si c'est le cas, prenez simplement la sous-chaîne appropriée en fonction de la longueur du nom du répertoire.

Voici un exemple de code:

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));
        }
    }
}

Je ne dis pas que c'est la chose la plus robuste au monde (les liens symboliques pourraient probablement le confondre), mais c'est probablement bien si c'est juste un outil que vous utiliserez de temps en temps.

37
Jon Skeet
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;
}

Ce code n'est probablement pas à l'épreuve des balles, mais c'est ce que j'ai proposé. C'est un peu plus robuste. Il prend deux chemins et retourne le chemin B relativement au chemin A.

exemple:

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"
8
Ben

Grâce aux autres réponses fournies ici et après quelques expériences, j'ai créé des méthodes d'extension très utiles:

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);
}

Les points importants:

  • Utilisez FileInfo et DirectoryInfo comme paramètres de méthode pour éviter toute ambiguïté quant à ce qui est travaillé. Uri.MakeRelativeUri _ s'attend à ce que les répertoires se terminent par une barre oblique.
  • DirectoryInfo.FullName ne normalise pas la barre oblique finale. Il sort quel que soit le chemin utilisé dans le constructeur. Cette méthode d'extension prend soin de cela pour vous.
4
Ronnie Overby

Il y a aussi ne façon de faire cela avec certaines restrictions . C'est le code de l'article:

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();
    }

Lors de l'exécution de ce morceau de code:

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));

il imprime:

..\SubDir2\SubDirIWant
..\..\SubDir1
2
Dmitry Pavlov