ここでは、ファイルやフォルダの相対パスから絶対パス(フルパス)を取得する方法と、絶対パスから相対パスを取得する方法を幾つか紹介します。
なお、例えば「C:\Windows\」というフォルダにある「file.txt」というファイルの絶対パス「C:\Windows\file.txt」を取得したいというように、両者を文字列としてつなぐだけでよいのであれば、「フォルダ名とファイル名を結合して、絶対パスを作成する」をご覧ください。
現在のディレクトリを基準にして相対パスから絶対パスを取得するには、Path.GetFullPathメソッドを使用します。なお現在のディレクトリを変更する方法は、「カレントディレクトリ(現在の作業ディレクトリ)を取得、設定する」をご覧ください。
'カレントディレクトリを変更 System.Environment.CurrentDirectory = "C:\Windows\System" '相対パス"..\file.txt"の絶対パスを取得する Console.WriteLine(System.IO.Path.GetFullPath("..\file.txt")) '結果: C:\Windows\file.txt
//カレントディレクトリを変更 System.Environment.CurrentDirectory = "C:\\Windows\\System"; //相対パス"..\file.txt"の絶対パスを取得する Console.WriteLine(System.IO.Path.GetFullPath("..\\file.txt")); //結果: C:\Windows\file.txt
「相対URLから絶対URLを取得する、絶対URLから相対URLを取得する」で紹介しているのと同じように、Uriクラスを使って相対パスと絶対パスの変換を行うことができます。ただ、後で説明するように少し癖があるため、細かい点を気にすると意外と面倒です。
「相対URLから絶対URLを取得する」と基本的には同じですが、パスの取得にはUri.LocalPathプロパティを使うのが良いでしょう。そうしないと、パスの区切り文字が「\」ではなく「/」になり、一部の文字はURLエンコードされたままになってしまいます。
「C:\Windows\System\」を基準とした相対パス「..\file.txt」の絶対パスを取得する例を以下に示します。
'"C:\Windows\System\"を基準とした"..\file.txt"の絶対パスを取得 Dim u1 As New Uri("C:\Windows\System\") Dim u2 As New Uri(u1, "..\file.txt") Console.WriteLine(u2.LocalPath) '結果: C:\Windows\file.txt
//"C:\Windows\System\"を基準とした"..\file.txt"の絶対パスを取得 Uri u1 = new Uri("C:\\Windows\\System\\"); Uri u2 = new Uri(u1, "..\\file.txt"); Console.WriteLine(u2.LocalPath); //結果: C:\Windows\file.txt
基準パスと相対パスをいろいろと変更して、どのように絶対パスが取得されるかを調べた結果を以下に示します。
基準となるパス | 相対パス | 取得される絶対パス |
---|---|---|
C:\Windows\System\ | a\b.txt | C:\Windows\System\a\b.txt |
C:\Windows\System\ | ..\..\a\b.txt | C:\a\b.txt |
C:\Windows\System\ | \a\b.txt | \\a\b.txt |
C:\Windows\System\ | .\b.txt | C:\Windows\System\b.txt |
C:\Windows\System\ | D:\a\ | D:\a\ |
C:\Windows\System | a\b.txt | C:\Windows\a\b.txt |
Uriクラスで厄介なのは、勝手にURLエンコードとデコードがされる点です。これについて詳しくは、「Uriコンストラクタで行われるURLエンコードとデコードについて」で説明しています。
上記の例のようにUri.LocalPathプロパティを使って絶対パスを取得した場合は、LocalPathプロパティがURLデコードしてくれるため、URLエンコードの心配はありません。
しかし、デコードはされます。例えば上記の例で相対パスを「..\%41.txt」にして絶対パスを取得すると、そのパスは「C:\Windows\A.txt」(「%41」が「A」にデコードされていることに注目してください)になってしまいます。
これを防ぐ方法としては、事前に「%」を「%25」にエンコードしておく方法が考えられます。この方法により、先ほどのコードを改良した例を示します。
Dim basePath As String = "C:\%25\System\" Dim filePath As String = "..\%41#.txt" '"%"を"%25"に変換しておく(デコード対策) basePath = basePath.Replace("%", "%25") filePath = filePath.Replace("%", "%25") '絶対パスを取得する Dim u1 As New Uri(basePath) Dim u2 As New Uri(u1, filePath) Dim absolutePath As String = u2.LocalPath '"%25"を"%"に戻す absolutePath = absolutePath.Replace("%25", "%") '結果を表示する Console.WriteLine(absolutePath) ' C:\%25\%41#.txt
string basePath = "C:\\%25\\System\\"; string filePath = "..\\%41#.txt"; //"%"を"%25"に変換しておく(デコード対策) basePath = basePath.Replace("%", "%25"); filePath = filePath.Replace("%", "%25"); //絶対パスを取得する Uri u1 = new Uri(basePath); Uri u2 = new Uri(u1, filePath); string absolutePath = u2.LocalPath; //"%25"を"%"に戻す absolutePath = absolutePath.Replace("%25", "%"); //結果を表示する Console.WriteLine(absolutePath); // C:\%25\%41#.txt
これも「絶対URLから相対URLを取得する」と同じ方法でできます。ただし、Uri.ToStringメソッドで相対パスを取得すると区切り文字は'\'ではなく'/'になります。
「C:\Windows\System\」を基準とした時の「C:\Windows\file.txt」の相対パスを取得する例を以下に示します。
'"C:\Windows\System\"を基準とした"C:\Windows\file.txt"の相対Uriを取得する Dim u1 As New Uri("C:\Windows\System\") Dim u2 As New Uri("C:\Windows\file.txt") '絶対Uriから相対Uriを取得する Dim relativeUri As Uri = u1.MakeRelativeUri(u2) '文字列に変換する Dim relativePath As String = relativeUri.ToString() '.NET Framework 1.1以前では次のようにする 'string relativePath = u1.MakeRelative(u2); '"/"を"\"に変換する relativePath = relativePath.Replace("/"c, "\"c) '結果を表示する Console.WriteLine(relativePath) '結果: ..\file.txt
//"C:\Windows\System\"を基準とした"C:\Windows\file.txt"の相対Uriを取得する Uri u1 = new Uri("C:\\Windows\\System\\"); Uri u2 = new Uri("C:\\Windows\\file.txt"); //絶対Uriから相対Uriを取得する Uri relativeUri = u1.MakeRelativeUri(u2); //文字列に変換する string relativePath = relativeUri.ToString(); //.NET Framework 1.1以前では次のようにする //string relativePath = u1.MakeRelative(u2); //"/"を"\"に変換する relativePath = relativePath.Replace('/', '\\'); //結果を表示する Console.WriteLine(relativePath); //結果: ..\file.txt
基準パスと絶対パスをいろいろと変更して、どのように相対パスが取得されるかを調べた結果を以下に示します。
基準となるパス | 絶対パス | 取得される相対パス |
---|---|---|
C:\Windows\System\ | C:\a\b.txt | ..\..\a\b.txt |
C:\a\b.txt | C:\Windows\System\ | ..\Windows\System\ |
C:\Windows\System | C:\a\b.txt | ..\a\b.txt |
C:\a\b.txt | C:\Windows\System | ..\Windows\System |
C:\Windows\System\ | D:\a\ | D:\a\ |
D:\a\ | C:\Windows\System\ | C:\Windows\System\ |
絶対パスを取得する時と同様に、相対パスを取得する場合でもUriクラスが勝手に行なうURLデコード対策が必要です。さらに、Uri.ToStringメソッドはLocalPathプロパティと違って一部の文字をデコードしませんので、エンコードされたパスをデコードする処理も必要になります。
パスをURLデコードする時に注意しなければならないのは、「+」を半角スペースに変換してはいけない点です。「URLエンコード、URLデコードを行う」で説明しているように、Uri.UnescapeDataStringメソッドは「+」を変換しませんが、HttpUtility.UrlDecodeメソッドは変換しますので、パスのデコードにはUnescapeDataStringメソッドを使います。ただし、.NET Framework 1.1以前などでどうしてもUrlDecodeメソッドを使わなければならない場合は、あらかじめ「+」を「%2B」に変換しておくなどの処置が必要になります。
Dim basePath As String = "C:\Windows\System\" Dim filePath As String = "C:\Windows\%41#.txt" '"%"を"%25"に変換しておく(デコード対策) basePath = basePath.Replace("%", "%25") filePath = filePath.Replace("%", "%25") '相対パスを取得する Dim u1 As New Uri(basePath) Dim u2 As New Uri(filePath) Dim relativeUri As Uri = u1.MakeRelativeUri(u2) Dim relativePath As String = relativeUri.ToString() '.NET Framework 1.1以前では次のようにする 'string relativePath = u1.MakeRelative(u2); 'URLデコードする(エンコード対策) relativePath = Uri.UnescapeDataString(relativePath) '.NET Framework 1.1以前では次のようにする 'relativePath = System.Web.HttpUtility.UrlDecode( ' relativePath.Replace("+", "%2B")); '"%25"を"%"に戻す relativePath = relativePath.Replace("%25", "%") '結果を表示する Console.WriteLine(relativePath) ' ../%41#.txt
string basePath = "C:\\Windows\\System\\"; string filePath = "C:\\Windows\\%41#.txt"; //"%"を"%25"に変換しておく(デコード対策) basePath = basePath.Replace("%", "%25"); filePath = filePath.Replace("%", "%25"); //相対パスを取得する Uri u1 = new Uri(basePath); Uri u2 = new Uri(filePath); Uri relativeUri = u1.MakeRelativeUri(u2); string relativePath = relativeUri.ToString(); //.NET Framework 1.1以前では次のようにする //string relativePath = u1.MakeRelative(u2); //URLデコードする(エンコード対策) relativePath = Uri.UnescapeDataString(relativePath); //.NET Framework 1.1以前では次のようにする //relativePath = System.Web.HttpUtility.UrlDecode( // relativePath.Replace("+", "%2B")); //"%25"を"%"に戻す relativePath = relativePath.Replace("%25", "%"); //結果を表示する Console.WriteLine(relativePath); // ../%41#.txt
例えば「C:\Windows\System\..\file.txt」のように「..」や「.」が含まれるパスを、「..」や「.」が含まれないパスに正規化することで、相対パスを絶対パスに変換することができます。ただし、「..」や「.」以外の表現が使われている相対パスに対しては、この方法は使えません。パスを正規化する方法については、「パス内の"."や".."を削除して、正規化する」をご覧ください。
Dim basePath As String = "C:\Windows\System" Dim relativePath As String = "..\file.txt" '基準パスと相対パスをPath.CombineでつなげたものをFileInfoクラスで正規化する Dim fi As New System.IO.FileInfo(System.IO.Path.Combine(basePath, relativePath)) '絶対パスを取得する Dim absolutePath As String = fi.FullName Console.WriteLine(absolutePath) '結果: C:\Windows\file.txt
string basePath = "C:\\Windows\\System"; string relativePath = "..\\file.txt"; //基準パスと相対パスをPath.CombineでつなげたものをFileInfoクラスで正規化する System.IO.FileInfo fi = new System.IO.FileInfo( System.IO.Path.Combine(basePath, relativePath)); //絶対パスを取得する string absolutePath = fi.FullName; Console.WriteLine(absolutePath); //結果: C:\Windows\file.txt
PathCombine functionを使うと、相対パスから絶対パスを取得することができます。この関数や、後で紹介するPathRelativePathTo関数は、Windows 2000(もしくは、Windows NT with Internet Explorer 4.0以上)、Windows 98(もしくは、Windows 95 with Internet Explorer 4.0以上)で使用できます。
相対パスから絶対パスを取得するメソッドの例を示します。このメソッドは、「pinvoke.net: PathCombine (shlwapi)」と「FolderBrowserDialog and P/Invoke shlwapi PathCombine cause RtlHeap corruption」を参考にして書きました。
'Imports System.Text 'Imports System.Runtime.InteropServices <DllImport("shlwapi.dll", _ CharSet:=CharSet.Auto)> _ Private Shared Function PathCombine( _ <Out> lpszDest As StringBuilder, _ lpszDir As String, lpszFile As String) As IntPtr End Function ''' <summary> ''' 相対パスから絶対パスを取得します。 ''' </summary> ''' <param name="basePath">基準とするパス。</param> ''' <param name="relativePath">相対パス。</param> ''' <returns>絶対パス。</returns> Public Shared Function GetAbsolutePath(basePath As String, _ relativePath As String) As String Dim sb As New StringBuilder(2048) PathCombine(sb, basePath, relativePath) Return sb.ToString() End Function
//using System.Text; //using System.Runtime.InteropServices; [DllImport("shlwapi.dll", CharSet = CharSet.Auto)] private static extern IntPtr PathCombine( [Out] StringBuilder lpszDest, string lpszDir, string lpszFile); /// <summary> /// 相対パスから絶対パスを取得します。 /// </summary> /// <param name="basePath">基準とするパス。</param> /// <param name="relativePath">相対パス。</param> /// <returns>絶対パス。</returns> public static string GetAbsolutePath(string basePath, string relativePath) { StringBuilder sb = new StringBuilder(2048); IntPtr res = PathCombine(sb, basePath, relativePath); if (res == IntPtr.Zero) { throw new Exception("絶対パスの取得に失敗しました。"); } return sb.ToString(); }
PathRelativePathTo functionを使うと、絶対パスから相対パスを取得することができます。
以下に、絶対パスから相対パスを取得するメソッドの例を示します。このコードは、「pinvoke.net: pathrelativepathto (shlwapi)」を参考にして書きました。
'Imports System.Text 'Imports System.Runtime.InteropServices <DllImport("shlwapi.dll", CharSet:=CharSet.Auto)> _ Private Shared Function PathRelativePathTo( _ <Out> pszPath As StringBuilder, _ <[In]> pszFrom As String, _ <[In]> dwAttrFrom As System.IO.FileAttributes, _ <[In]> pszTo As String, _ <[In]> dwAttrTo As System.IO.FileAttributes) As Boolean End Function ''' <summary> ''' 絶対パスから相対パスを取得します。 ''' </summary> ''' <param name="basePath">基準とするフォルダのパス。</param> ''' <param name="absolutePath">相対パス。</param> ''' <returns>絶対パス。</returns> Public Shared Function GetRelativePath(basePath As String, _ absolutePath As String) As String Dim sb As New StringBuilder(260) Dim res As Boolean = PathRelativePathTo( _ sb, basePath, System.IO.FileAttributes.Directory, _ absolutePath, System.IO.FileAttributes.Normal) If Not res Then Throw New Exception("相対パスの取得に失敗しました。") End If Return sb.ToString() End Function
//using System.Text; //using System.Runtime.InteropServices; [DllImport("shlwapi.dll", CharSet = CharSet.Auto)] private static extern bool PathRelativePathTo( [Out] StringBuilder pszPath, [In] string pszFrom, [In] System.IO.FileAttributes dwAttrFrom, [In] string pszTo, [In] System.IO.FileAttributes dwAttrTo ); /// <summary> /// 絶対パスから相対パスを取得します。 /// </summary> /// <param name="basePath">基準とするフォルダのパス。</param> /// <param name="absolutePath">相対パス。</param> /// <returns>絶対パス。</returns> public static string GetRelativePath(string basePath, string absolutePath) { StringBuilder sb = new StringBuilder(260); bool res = PathRelativePathTo(sb, basePath, System.IO.FileAttributes.Directory, absolutePath, System.IO.FileAttributes.Normal); if (!res) { throw new Exception("相対パスの取得に失敗しました。"); } return sb.ToString(); }
蛇足ですが、上記の方法を使用せずに、自分でメソッドを書いてみました。かなり大雑把で、細かいことは無視していますので、参考程度にしてください。
Imports System.Text Imports System.IO Public Class PathEx Private Shared ReadOnly DirectorySeparatorChar As Char = _ Path.DirectorySeparatorChar Private Shared ReadOnly CurrentDirectoryString As String = _ "." + DirectorySeparatorChar Private Shared ReadOnly ParentDirectoryString As String = _ ".." + DirectorySeparatorChar Private Shared ReadOnly RootDirectoryString As String = _ DirectorySeparatorChar.ToString() ''' <summary> ''' 相対パスから絶対パスを取得します。 ''' </summary> ''' <param name="basePath">基準とするフォルダのパス。</param> ''' <param name="relativePath">相対パス。</param> ''' <returns>絶対パス。</returns> Public Shared Function GetAbsolutePath(basePath As String, _ relativePath As String) As String If basePath Is Nothing OrElse basePath.Length = 0 Then Return relativePath End If If relativePath Is Nothing OrElse relativePath.Length = 0 Then Return basePath End If basePath = basePath.TrimEnd(DirectorySeparatorChar) If relativePath.StartsWith(ParentDirectoryString) Then '相対パスが"..\"で始まっている時 '基準パスの最後の"\"から後ろを削除する Dim pos As Integer = basePath.LastIndexOf(DirectorySeparatorChar) If pos < 0 Then Throw New ArgumentException("""..\""が多すぎます。", _ "relativePath") End If basePath = basePath.Remove(pos) '相対パスのはじめの"..\"を削除する relativePath = relativePath.Substring(ParentDirectoryString.Length) 'あらためて絶対パスを取得する Return GetAbsolutePath(basePath, relativePath) ElseIf relativePath.StartsWith(CurrentDirectoryString) Then '相対パスが".\"で始まっている時 '相対パスのはじめの".\"を削除する relativePath = relativePath.Substring(CurrentDirectoryString.Length) 'あらためて絶対パスを取得する Return GetAbsolutePath(basePath, relativePath) ElseIf relativePath.StartsWith(RootDirectoryString) Then '相対パスが"\"で始まっている時 '基準パスのルートパスを取得する basePath = Path.GetPathRoot(basePath) basePath = basePath.TrimEnd(DirectorySeparatorChar) '相対パスのはじめの"\"を削除する relativePath = relativePath.Substring(RootDirectoryString.Length) End If 'パスを連結する Return basePath & DirectorySeparatorChar & relativePath End Function ''' <summary> ''' 絶対パスから相対パスを取得します。 ''' </summary> ''' <param name="basePath">基準とするフォルダのパス。</param> ''' <param name="absolutePath">絶対パス。</param> ''' <returns>相対パス。</returns> Public Shared Function GetRelativePath(basePath As String, _ absolutePath As String) As String If basePath Is Nothing OrElse basePath.Length = 0 Then Return absolutePath End If If absolutePath Is Nothing OrElse absolutePath.Length = 0 Then Return "" End If basePath = basePath.TrimEnd(DirectorySeparatorChar) 'パスを"\"で分割する Dim basePathDirs As String() = _ basePath.Split(DirectorySeparatorChar) Dim absolutePathDirs As String() = _ absolutePath.Split(DirectorySeparatorChar) '基準パスと絶対パスで、先頭から共通する部分を探す Dim commonCount As Integer = 0 While commonCount < basePathDirs.Length AndAlso _ commonCount < absolutePathDirs.Length AndAlso _ basePathDirs(commonCount).Equals(absolutePathDirs(commonCount), _ StringComparison.OrdinalIgnoreCase) '共通部分の数を覚えておく commonCount += 1 End While '共通部分がない時 If commonCount = 0 Then Return absolutePath End If '共通部分以降の基準パスのフォルダの深さを取得する Dim baseOnlyCount As Integer = basePathDirs.Length - commonCount 'その数だけ"..\"を付ける Dim buf As New StringBuilder() For i As Integer = 0 To baseOnlyCount - 1 buf.Append(ParentDirectoryString) Next '共通部分以降の絶対パス部分を追加する buf.Append(String.Join(DirectorySeparatorChar.ToString(), _ absolutePathDirs, commonCount, _ absolutePathDirs.Length - commonCount)) Return buf.ToString() End Function End Class
using System; using System.Text; using System.IO; public class PathEx { private readonly static char DirectorySeparatorChar = Path.DirectorySeparatorChar; private readonly static string CurrentDirectoryString = "." + DirectorySeparatorChar; private readonly static string ParentDirectoryString = ".." + DirectorySeparatorChar; private readonly static string RootDirectoryString = DirectorySeparatorChar.ToString(); /// <summary> /// 相対パスから絶対パスを取得します。 /// </summary> /// <param name="basePath">基準とするフォルダのパス。</param> /// <param name="relativePath">相対パス。</param> /// <returns>絶対パス。</returns> public static string GetAbsolutePath(string basePath, string relativePath) { if (basePath == null || basePath.Length == 0) { return relativePath; } if (relativePath == null || relativePath.Length == 0) { return basePath; } basePath = basePath.TrimEnd(DirectorySeparatorChar); if (relativePath.StartsWith(ParentDirectoryString)) { //相対パスが"..\"で始まっている時 //基準パスの最後の"\"から後ろを削除する int pos = basePath.LastIndexOf(DirectorySeparatorChar); if (pos < 0) { throw new ArgumentException( "\"..\\\"が多すぎます。", "relativePath"); } basePath = basePath.Remove(pos); //相対パスのはじめの"..\"を削除する relativePath = relativePath.Substring(ParentDirectoryString.Length); //あらためて絶対パスを取得する return GetAbsolutePath(basePath, relativePath); } else if (relativePath.StartsWith(CurrentDirectoryString)) { //相対パスが".\"で始まっている時 //相対パスのはじめの".\"を削除する relativePath = relativePath.Substring(CurrentDirectoryString.Length); //あらためて絶対パスを取得する return GetAbsolutePath(basePath, relativePath); } else if (relativePath.StartsWith(RootDirectoryString)) { //相対パスが"\"で始まっている時 //基準パスのルートパスを取得する basePath = Path.GetPathRoot(basePath); basePath = basePath.TrimEnd(DirectorySeparatorChar); //相対パスのはじめの"\"を削除する relativePath = relativePath.Substring(RootDirectoryString.Length); } //パスを連結する return basePath + DirectorySeparatorChar + relativePath; } /// <summary> /// 絶対パスから相対パスを取得します。 /// </summary> /// <param name="basePath">基準とするフォルダのパス。</param> /// <param name="absolutePath">絶対パス。</param> /// <returns>相対パス。</returns> public static string GetRelativePath(string basePath, string absolutePath) { if (basePath == null || basePath.Length == 0) { return absolutePath; } if (absolutePath == null || absolutePath.Length == 0) { return ""; } basePath = basePath.TrimEnd(DirectorySeparatorChar); //パスを"\"で分割する string[] basePathDirs = basePath.Split(DirectorySeparatorChar); string[] absolutePathDirs = absolutePath.Split(DirectorySeparatorChar); //基準パスと絶対パスで、先頭から共通する部分を探す int commonCount = 0; for (int i = 0; i < basePathDirs.Length && i < absolutePathDirs.Length && basePathDirs[i].Equals(absolutePathDirs[i], StringComparison.OrdinalIgnoreCase); i++) { //共通部分の数を覚えておく commonCount++; } //共通部分がない時 if (commonCount == 0) { return absolutePath; } //共通部分以降の基準パスのフォルダの深さを取得する int baseOnlyCount = basePathDirs.Length - commonCount; //その数だけ"..\"を付ける StringBuilder buf = new StringBuilder(); for (int i = 0; i < baseOnlyCount; i++) { buf.Append(ParentDirectoryString); } //共通部分以降の絶対パス部分を追加する buf.Append(string.Join(DirectorySeparatorChar.ToString(), absolutePathDirs, commonCount, absolutePathDirs.Length - commonCount)); return buf.ToString(); } }