Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting path relative to the current working directory? [duplicate]

Tags:

c#

file-io

People also ask

How do I find the relative path of a file?

var relativePath = Path. GetRelativePath( @"C:\Program Files\Dummy Folder\MyProgram", @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1. dat"); In the above example, the relativePath variable is equal to Data\datafile1.


If you don't mind the slashes being switched, you could [ab]use 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)
    );

As a function/method:

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

You can use Environment.CurrentDirectory to get the current directory, and FileSystemInfo.FullPath to get the full path to any location. So, fully qualify both the current directory and the file in question, and then check whether the full file name starts with the directory name - if it does, just take the appropriate substring based on the directory name's length.

Here's some sample 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));
        }
    }
}

I'm not saying it's the most robust thing in the world (symlinks could probably confuse it) but it's probably okay if this is just a tool you'll be using occasionally.


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

This code is probably not bullet-proof but this is what I came up with. It's a little more robust. It takes two paths and returns path B as relative to path A.

example:

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"

Thanks to the other answers here and after some experimentation I've created some very useful extension methods:

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

Important points:

  • Use FileInfo and DirectoryInfo as method parameters so there is no ambiguity as to what is being worked with. Uri.MakeRelativeUri expects directories to end with a trailing slash.
  • DirectoryInfo.FullName doesn't normalize the trailing slash. It outputs whatever path was used in the constructor. This extension method takes care of that for you.

There is also a way to do this with some restrictions. This is the code from the 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();
    }

When executing this piece of 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));

it prints out:

..\SubDir2\SubDirIWant
..\..\SubDir1