パスからファイル名・ディレクトリ名を抽出したり、パスを結合したりするにはPathクラスを使います。 単純な文字列操作でもパスの抽出・結合といった操作を行うことはできますが、Pathクラスに用意されているメソッドには次のようなメリットがあります。

  1. 相対パス(...など)の扱いやパスが正しく区切り文字で終わっているかとどうかといった細かい考慮が不要
  2. 通常のパス(例:C:\Windows\Microsoft.NET\)だけでなく、ドライブ名を省略したパス(例:\Windows\Microsoft.NET\)、UNCパス(例:\\SERV1\dir\)などがサポートされている
  3. 自動的に適切なディレクトリ区切り文字が使われるので、LinuxやMac環境でもパス区切り文字の違い(\/)を意識しなければならない個所が減る (Monoなどを使った場合)

このように、特殊な場合を除けばPathクラスのメソッドを使わずわざわざ文字列操作によってパスの処理を記述するメリットはありません。 Pathクラスではインスタンスを作成する必要はありません。 Pathクラスでパスの操作を行う場合は常にクラスメソッドを呼び出して行います。

なお、ここではフォルダという用語は使わずクラス名にも使われているディレクトリで統一します。

本文中のサンプルではパスを記述するために逐語的文字列リテラルを使用している個所があります。 C#では文字列中の\はエスケープ記号として扱われるため、ディレクトリ区切り文字を記述するには\\のように二つ重ねる必要がありますが、アットマーク@を前置して逐語的文字列リテラルとして記述する場合は、\はエスケープ記号としては扱われなくなり、ディレクトリ区切り文字はそのまま\と記述できるようになるためパスが読みやすくなります。

パスと逐語的文字列リテラルの例
using System;

class Sample {
  static void Main()
  {
    // 通常の文字列リテラルを使って記述したパス
    string path1 = "C:\\Windows\\Microsoft.NET\\Framework";
    // 逐語的文字列リテラルを使って記述したパス
    string path2 = @"C:\Windows\Microsoft.NET\Framework";

    // path1とpath2はどちらも同じ文字列となる
    Console.WriteLine(path1);
    Console.WriteLine(path2);
  }
}
実行結果
C:\Windows\Microsoft.NET\Framework
C:\Windows\Microsoft.NET\Framework

§1 パスの分割

ここではPathクラスのメソッドを使ってパスからファイル名や拡張子などを取得する方法について解説します。

§1.1 ファイル名・ディレクトリ名

パスからファイル名を抽出するにはGetFileNameメソッド、ディレクトリ名を抽出するにはGetDirectoryNameメソッドを使います。 GetDirectoryNameメソッドでは、パスが絶対パスであればそのディレクトリ部分、相対パスであれば区切り文字(\または/)以前の部分をディレクトリ名として返します。

ファイル名・ディレクトリ名の取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string p1 = @"E:\dir\subdir\file.txt"; // 絶対パス形式

    Console.WriteLine(Path.GetFileName(p1));
    Console.WriteLine(Path.GetDirectoryName(p1));
    Console.WriteLine();

    string p2 = @".\dir\subdir\file.txt"; // 相対パス形式

    Console.WriteLine(Path.GetFileName(p2));
    Console.WriteLine(Path.GetDirectoryName(p2));
  }
}
実行結果
file.txt
E:\dir\subdir

file.txt
.\dir\subdir

GetFileName・GetDirectoryNameメソッドはパスが実際にファイルであるか、ディレクトリであるかは検証しません。 そのため、次の例のようにディレクトリ区切り文字で終わっていないパスは、それがディレクトリを表すものであってもファイル名として扱われる点に注意が必要です。

サブディレクトリ名の取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ディレクトリを表すパス (ディレクトリ区切り文字で終わっていない場合)
    string path1 = @"dir\subdir";

    Console.WriteLine("path = {0}", path1);
    Console.WriteLine("directory = {0}", Path.GetDirectoryName(path1));
    Console.WriteLine("file = {0}", Path.GetFileName(path1));
    Console.WriteLine();

    // ディレクトリを表すパス (ディレクトリ区切り文字で終わっている場合)
    string path2 = @"dir\subdir\";

    Console.WriteLine("path = {0}", path2);
    Console.WriteLine("directory = {0}", Path.GetDirectoryName(path2));
    Console.WriteLine("file = {0}", Path.GetFileName(path2));
  }
}
実行結果
path = dir\subdir
directory = dir
file = subdir

path = dir\subdir\
directory = dir\subdir
file = 

§1.2 拡張子を除いたファイル名

拡張子を除いたファイル名が必要な場合はGetFileNameWithoutExtensionメソッドを使います。 拡張子がなければファイル名がそのまま返されます。

拡張子を除いたファイル名の取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string p1 = @"E:\dir\subdir\file.txt";

    Console.WriteLine(Path.GetFileName(p1));
    Console.WriteLine(Path.GetFileNameWithoutExtension(p1));
    Console.WriteLine();

    string p2 = @"dir\file";

    Console.WriteLine(Path.GetFileName(p2));
    Console.WriteLine(Path.GetFileNameWithoutExtension(p2));
  }
}
実行結果
file.txt
file

file
file

§1.3 ルートディレクトリ・ドライブ名

ドライブ名だけを返すメソッドは用意されていませんが、GetPathRootメソッドでパスからルートディレクトリを取得できるので、これを使うことでドライブ名を取得することができます。 ルートディレクトリを含まない相対パスの場合は、空の文字列が返されます。

ドライブ名の取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string path1 = @"E:\dir\subdir\file.txt"; // 絶対パス形式

    Console.WriteLine(Path.GetPathRoot(path1));

    string path2 = @"dir\file.txt"; // 相対パス形式

    Console.WriteLine(Path.GetPathRoot(path2));
  }
}
実行結果
E:\

§1.4 拡張子

パスまたはファイル名から拡張子を抽出するにはGetExtensionメソッドを使います。 このメソッドではピリオド.も含めて拡張子として扱われます(ファイル名がfile.txtなら拡張子は.txtとなる)。 拡張子がないファイル名の場合は空の文字列が返されます。 ピリオドが複数あるファイル名の場合は、一番最後のピリオド以降を拡張子として返します。

拡張子の取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    Console.WriteLine(Path.GetExtension(@"E:\dir\subdir\file.txt"));
    Console.WriteLine(Path.GetExtension(@"dir\file.txt"));
    Console.WriteLine(Path.GetExtension(@"dir\file")); // 拡張子を持たないファイル名
    Console.WriteLine(Path.GetExtension(@"file.txt.bak")); // ピリオドを二つ以上含むファイル名
  }
}
実行結果
.txt
.txt

.bak


§1.4.1 パスが拡張子を持つかどうか

パスが拡張子を持つかどうかを調べるにはHasExtensionメソッドを使います。

パスが拡張子を持つかどうかを調べる
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string[] paths = new[] {
      @"E:\dir\subdir\file.txt",
      @"E:\dir\subdir\file",
      @"file.txt.bak",
      @"file",
    };

    foreach (string p in paths) {
      Console.WriteLine("HasExtension(\"{0}\") = {1}", p, Path.HasExtension(p));
    }
  }
}
実行結果
HasExtension("E:\dir\subdir\file.txt") = True
HasExtension("E:\dir\subdir\file") = False
HasExtension("file.txt.bak") = True
HasExtension("file") = False

§1.5 拡張子の置換・削除

パスまたはファイル名の拡張子を別の拡張子に置き換えるにはChangeExtensionメソッドを使います。 元のパス・ファイル名に拡張子がない場合は拡張子が追加され、ピリオドが二つ以上あるファイル名の場合は一番最後のピリオド以降が置き換えられます。

パス内に含まれる拡張子を置き換える
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string[] paths = new[] {
      @"E:\dir\subdir\file.txt",
      @"E:\dir\subdir\file.txt.bak",
      @"E:\dir\subdir\file.bmp",
      @"E:\dir\subdir\file",
      @"file.txt",
      @"file.txt.bak",
      @"file",
    };

    foreach (string p in paths) {
      // パス中の拡張子を.datに置き換える
      Console.WriteLine(Path.ChangeExtension(p, ".dat"));
    }
  }
}
実行結果
E:\dir\subdir\file.dat
E:\dir\subdir\file.txt.dat
E:\dir\subdir\file.dat
E:\dir\subdir\file.dat
file.dat
file.txt.dat
file.dat

置き換える拡張子に空の文字列を指定した場合、ピリオドだけの拡張子に置き換えられます。 一方、null/Nothingを指定した場合は、ピリオドも含めて拡張子が削除されます。

パス内に含まれる拡張子を削除する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    string file = "file.txt";

    Console.WriteLine(Path.ChangeExtension(file, ".dat")); // 拡張子を.datに置き換える
    Console.WriteLine(Path.ChangeExtension(file, string.Empty)); // ピリオドを残して拡張子を削除する
    Console.WriteLine(Path.ChangeExtension(file, null)); // ピリオドも含めて拡張子を削除する
  }
}
実行結果
file.dat
file.
file

§1.6 ファイル名の置換

ChangeExtensionメソッドが用意されている一方、Pathクラスには拡張子を除くファイル名部分だけを置き換えるChangeFileNameといったメソッドは用意されていないので、GetExtensionメソッドなどを組み合わせて実装する必要があります。

パス内に含まれるファイル名を置き換える例
using System;
using System.IO;

class Sample {
  // パスの拡張子を除いたファイル名部分だけを置き換えるメソッド
  static string ChangeFileName(string path, string newFileName)
  {
    string dir = Path.GetDirectoryName(path); // ディレクトリ部分を抽出
    string ext = Path.GetExtension(path); // 拡張子部分を抽出

    // ディレクトリ、新しいファイル名、拡張子を連結して返す
    return Path.Combine(dir, newFileName + ext);
  }

  static void Main()
  {
    Console.WriteLine(ChangeFileName(@"E:\dir\subdir\file.txt", "ファイル"));
    Console.WriteLine(ChangeFileName(@"E:\dir\subdir\file", "ファイル"));
    Console.WriteLine(ChangeFileName(@"image.bmp", "画像"));
  }
}
実行結果
E:\dir\subdir\ファイル.txt
E:\dir\subdir\ファイル
画像.bmp

§2 パスの結合

パスを結合するにはCombineメソッドを使います。 このメソッドを使うことで、部分に分割されているパスを単一のパスに結合することができます。 パスは相対パス同士でも結合することができ、区切り文字で終わっていないパスの場合は区切り文字を追加した上で結合されます。 また、このメソッドでは"."や".."は正規化されずそのまま結合されます。

パスの結合
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 絶対パスのディレクトリとファイル名を結合する
    Console.WriteLine(Path.Combine(@"E:\dir\", @"file.txt"));

    // 区切り文字で終わっていない絶対パスとファイル名を結合する
    Console.WriteLine(Path.Combine(@"E:\dir", @"file.txt"));

    // 絶対パスのディレクトリと相対パスを結合する
    Console.WriteLine(Path.Combine(@"E:\dir\", @"subdir\file.txt"));

    // ".."(親ディレクトリ)を含む相対パスを結合する
    Console.WriteLine(Path.Combine(@"E:\dir\", @"..\file.txt"));

    // 相対パス同士を結合する
    Console.WriteLine(Path.Combine(@"dir\subdir", @".\file.txt"));
  }
}
実行結果
E:\dir\file.txt
E:\dir\file.txt
E:\dir\subdir\file.txt
E:\dir\..\file.txt
dir\subdir\.\file.txt

"."や".."を含むパスの正規化にはGetFullPathメソッドを使用します。


Combineメソッドでは最大4つのパスを結合することができます。 それ以上の数の場合でも、配列やListに格納することで任意の数のパスを結合することができます。

パスの結合
using System;
using System.IO;

class Sample {
  static void Main()
  {
    Console.WriteLine(Path.Combine("dir", "file.txt"));
    Console.WriteLine(Path.Combine("dir", "subdir", "file.txt"));
    Console.WriteLine(Path.Combine("dir", "subdir1", "subdir2", "file.txt"));

    // 配列に格納されたパスの断片
    string[] paths = new[] {@"C:\Windows", "Microsoft.NET", "Framework", @"v4.0.30319\clr.dll"};

    Console.WriteLine(Path.Combine(paths));
  }
}
実行結果
dir\file.txt
dir\subdir\file.txt
dir\subdir1\subdir2\file.txt
C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll

§2.1 ディレクトリ区切り文字

Combineメソッドでは自動的に適切なディレクトリ区切り文字が使用されます。 例えば、Windows環境(.NET Framework)では"\"が使われますが、Linux・Mac環境(Mono)では"/"が用いられます。 現在の実行環境でのディレクトリ区切り文字を知りたい場合はDirectorySeparatorCharプロパティを参照します。

パスの結合
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ディレクトリ名とファイル名の結合
    Console.WriteLine(Path.Combine("dir", "file.txt"));

    // ディレクトリ区切り文字の取得
    Console.WriteLine("DirectorySeparatorChar = {0}", Path.DirectorySeparatorChar);
  }
}
Windows上の.NET Frameworkでの実行結果
dir\file.txt
DirectorySeparatorChar = \
Linux上のMonoでの実行結果
dir/file.txt
DirectorySeparatorChar = /

§3 絶対パスの取得・パスの正規化

パスから絶対パスを取得するにはGetFullPathメソッドを使います。 このメソッドを呼び出すと...など相対パスを表す文字列が除去された(正規化された)絶対パスが返されます。 相対パスから絶対パスへ変換する際、相対パスはカレントディレクトリを基準としたものとして扱われます。

パスの正規化・絶対パスの取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // パスの正規化
    Console.WriteLine(Path.GetFullPath(@"E:\dir\.\subdir\file.txt"));
    Console.WriteLine(Path.GetFullPath(@"E:\dir1\..\dir2\file.txt"));

    // 相対パスから絶対パスへの変換
    Console.WriteLine(Path.GetFullPath(@"file.txt"));
    Console.WriteLine(Path.GetFullPath(@".\file.txt"));
    Console.WriteLine(Path.GetFullPath(@"..\file.txt"));
  }
}
Windows上の.NET Frameworkでの実行結果例
E:\dir\subdir\file.txt
E:\dir2\file.txt
c:\Users\smdn\file.txt
c:\Users\smdn\file.txt
c:\Users\file.txt

Linux・Mac環境(Mono)でもディレクトリ区切り文字が/になっていればGetFullPathは正しく動作します。

パスの正規化・絶対パスの取得
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // パスの正規化
    Console.WriteLine(Path.GetFullPath(@"/root/dir/./subdir/file.txt"));
    Console.WriteLine(Path.GetFullPath(@"/root/dir1/../dir2/file.txt"));

    // 相対パスから絶対パスへの変換
    Console.WriteLine(Path.GetFullPath(@"file.txt"));
    Console.WriteLine(Path.GetFullPath(@"./file.txt"));
    Console.WriteLine(Path.GetFullPath(@"../file.txt"));
  }
}
Linux上のMonoでの実行結果例
/root/dir/subdir/file.txt
/root/dir2/file.txt
/home/smdn/file.txt
/home/smdn/file.txt
/home/file.txt

カレントディレクトリの取得・設定についてはプロセス・アセンブリの情報 §.カレントディレクトリを参照してください。

§4 相対パスの取得

Pathクラスには絶対パスから相対パスを取得するメソッドは用意されていません。 代わりにUriクラスを使って相対パスを作成することができます。 以下に紹介する方法では、ファイルパスをURIとして扱い、Uriクラスのメソッドを使って相対パスに変換しています。

Uriクラスを使った相対パスの取得
using System;
using System.IO;

class Sample {
  // basePathを基準としてpathへの相対パスを取得するメソッド
  static string GetRelativePath(string basePath, string path)
  {
    if (basePath == null)
      throw new ArgumentNullException("basePath");
    if (path == null)
      throw new ArgumentNullException("path");

    // 現在Windows上で動作しているかどうか
    var isRunningOnWindows = (int)Environment.OSVersion.Platform < 4;

    if (isRunningOnWindows && !Path.IsPathRooted(basePath))
      throw new ArgumentException("パスは絶対パスである必要があります", "basePath");

    // URIとして処理する前にパス中の%をURLエンコードする
    basePath = basePath.Replace("%", "%25");
    path = path.Replace("%", "%25");

    if (!isRunningOnWindows) {
      // 非Windows環境ではパスをURIとして解釈させるためにfile://スキームを前置する
      basePath = Uri.UriSchemeFile + Uri.SchemeDelimiter + basePath.Replace(":", "%3A");
      path     = Uri.UriSchemeFile + Uri.SchemeDelimiter + path.Replace(":", "%3A");
    }

    // パスをURIに変換
    var uriBase = new Uri(basePath);
    var uriTarget = new Uri(path);

    // MakeRelativeUriメソッドで相対パスを取得する
    // 同時にURIに変換する際にエスケープされた文字列をアンエスケープする
    var relativePath = Uri.UnescapeDataString(uriBase.MakeRelativeUri(uriTarget).ToString());

    // ディレクトリ区切り文字を環境に合わせて置換する
    relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar);

    // URLエンコードした%を元に戻す
    return relativePath.Replace("%25", "%");
  }

  static void Main()
  {
    if ((int)Environment.OSVersion.Platform < 4)
      Console.WriteLine(GetRelativePath(@"E:\dir1\subdir1\subdir2\", @"E:\dir2\ファイル.txt"));
    else
      Console.WriteLine(GetRelativePath("/dir1/subdir2/subdir2/", "/dir2/ファイル.txt"));
  }
}
Windows上の.NET Frameworkでの実行結果
..\..\..\dir2\ファイル.txt
Linux上のMonoでの実行結果
../../../dir2/ファイル.txt

§5 ルートディレクトリ

GetPathRootメソッドを使うことでパスからルートディレクトリ部分のみを取得することができます。 例えば、C:\Windows\Microsoft.NET\Frameworkというパスの場合はC:\がルートディレクトリ部分となります。 また、パスがルートディレクトリから始まっているかどうかを調べるにはIsPathRootedメソッドを使うことができます。

ルートディレクトリの取得(Windows上での場合)
using System;
using System.IO;

class Sample {
  static void Main()
  {
    Console.WriteLine(Path.GetPathRoot(@"C:\Windows\Microsoft.NET\Framework")); // ドライブ名から始まるパス
    Console.WriteLine(Path.GetPathRoot(@"C:")); // ドライブレターのみのパス
    Console.WriteLine(Path.GetPathRoot(@"dir\file.txt")); // 相対パス

    Console.WriteLine(Path.IsPathRooted(@"C:\Windows\Microsoft.NET\Framework"));
    Console.WriteLine(Path.IsPathRooted(@"C:"));
    Console.WriteLine(Path.IsPathRooted(@"dir\file.txt"));
  }
}
Windows上の.NET Frameworkでの実行結果
C:\
C:

True
True
False

Linux・Mac環境(Mono)では、常にパス先頭の/がルートディレクトリとして扱われます。

ルートディレクトリの取得(Linux・Mac上での場合)
using System;
using System.IO;

class Sample {
  static void Main()
  {
    Console.WriteLine(Path.GetPathRoot("/root/dir/"));
    Console.WriteLine(Path.GetPathRoot("/"));
    Console.WriteLine(Path.GetPathRoot("dir/file.txt"));
    Console.WriteLine(Path.GetPathRoot(@"C:\Windows\Microsoft.NET\Framework")); // Windows形式のパス

    Console.WriteLine(Path.IsPathRooted("/root/dir/"));
    Console.WriteLine(Path.IsPathRooted("/"));
    Console.WriteLine(Path.IsPathRooted("dir/file.txt"));
    Console.WriteLine(Path.IsPathRooted(@"C:\Windows\Microsoft.NET\Framework"));
  }
}
Linux上のMonoでの実行結果
/
/


True
True
False
False

§6 パスやファイル名に使用できない文字

一部の記号はパスやファイル名に使うことができません。 ファイル名に使用できない文字はGetInvalidFileNameCharsメソッド、パスに使用できない文字はGetInvalidPathCharsメソッドによって取得できます。 これらのメソッドはユーザーによって入力されたファイル名やパスに使用できない文字が含まれているか検証したり別の文字に置き換えたりする場合に使用することができます。 なお、これらの文字はプラットフォームによって異なります。 詳しくはランタイム・システム・プラットフォームの情報 §.ファイル・ディレクトリの区切り文字・無効な文字で解説しています。

§6.1 ランダムなファイル名の生成

使用不可能な文字を含まないファイル名を生成する必要がある場合はGetRandomFileNameメソッドを使うことができます。 このメソッドはランダムな文字列からなるファイル名を生成して返します。 このメソッドで作成されるのはファイル名(文字列)だけで、ファイル自体は作成されません。

ランダムなファイル名の生成
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ランダムなファイル名を10個生成する
    for (int i = 0; i < 10; i++) {
      Console.WriteLine(Path.GetRandomFileName());
    }
  }
}
実行結果例
hoksydgk.ic2
njge1wrb.o33
nqnvcxwr.cui
l5kjak0c.eix
khuaxu11.onp
1ukhqw0f.opm
jifclgdc.m1o
ge1qi1ah.udi
2aczb3ka.0if
l5cvnbej.mtk

一時ファイルの作成が必要な場合はGetTempFileNameメソッドを使うこともできます。 このメソッドはGetRandomFileNameメソッドとは異なり実際にファイルを作成した上でそのファイル名を返します。

一時ファイルの作成、および一時ファイルとしてのランダムなファイル名の生成については一時ディレクトリを取得する・一時ファイルを作成するを参照してください。