先日アプリケーションのStartupPathをちゃんと取得するという話を書いたのだけど、
たかがPath、されどPath、ちゃんとやろうとすると
なかなか奥が深いというか面倒くさいので、もうちょっと書きます。
けっこう長くなったけど、トピックスは2つ。
・絶対パスか相対パスかを調べる
・パスを連結する
こんなんできるわ!という人も、読んでみると知らない発見があったり
なかったりすると思うので読んでみたらどうでしょうか。
絶対パスか相対パスかを調べる
「C# 絶対パスかどうか」とかで検索すると、
System.IO.Path.IsPathRootedを使えという情報がすぐにヒットするけれど、
これの判定が本当に自分の望むものになっているかどうか、ちょっと注意が必要。
bool res1 = Path.IsPathRooted(@"C:\hoge")); bool res2 = Path.IsPathRooted(@"hoge")); bool res3 = Path.IsPathRooted(@"\hoge"));
上のres1はTrue、res2はFalseになる。それは良いだろう。
問題はres3だが、これはTrueになるので注意。
他のPath関係のメソッドもそうなんだけど、どうも.NETは
先頭に区切り文字があるPathは基本的にそれがルートだとみなす。
Unix系の環境で動くことも意識してのことでしょうかね。
(実際、上のコードは'\'を'/'に置き換えても同じ結果を返す)
MSDNのサンプルはこのへんちょっとずるくて、
UNC形式のパスの例はあるんだけど、上のres3みたいな例がない。
なのではまりやすいですねこれは。
じゃあ区切り文字から始まるときにFalseを返してほしいときは
どうすればいいかというと、とりあえず僕は↓こうしてます。
string path1 = @"C:\hoge"; string path2 = @"hoge"; string path3 = @"\hoge"; bool res1 = Path.GetFullPath(path1) == path1; //True bool res2 = Path.GetFullPath(path2) == path2; //False bool res3 = Path.GetFullPath(path3) == path3; //False
例えば上のコードを「C:\MyDir」のディレクトリで実行した場合、
Path.GetFullPath(path2)は「C:\MyDir\hoge」となる。
Path.GetFullPath(path3)は、先頭にドライブレターをつけて
「C:\hoge」が返ってくるのが謎だけどとりあえず判定には使える。
注意点として、普通にWindows環境で実行するときに
Path.GetFullPath(@"C:/hoge")とかやると
区切り文字を直されて「C:\hoge」が返ってくるので、上の判定だとFalseになる。
必ずWindows上で実行するなら判定の前にString.Replaceメソッドで'/'を'\'に
全置換しておけばいいけど、Linuxで動かすかもしれなくて、
パスもどっちの文字を指定されるかわからないとかであれば、
以下の2行の処理を挟む必要があるかな。
path = path.Replace('/', Path.DirectorySeparatorChar); path = path.Replace('\\', Path.DirectorySeparatorChar);
ちなみにLinuxのMono上では、Path.GetFullPathは以下のようにふるまうようだ。
// ※「/home/username/」から実行 string fp1 = Path.GetFullPath(@"hoge"); // "/home/username/hoge" string fp2 = Path.GetFullPath(@"\hoge"); // "/home/username/\hoge" string fp3 = Path.GetFullPath(@"/hoge"); // "/hoge"
パスを連結する
例えばユーザーに任意の相対パスを入力してもらって、
それにルートをつけて絶対パスに直すとかいうケースも、
ちゃんとやろうとすると色々気をつける点がある。
要するに、先頭に区切り文字を付けられるかどうかわからんから
単純に演算子で足すときは注意が必要だという話です。
const string Root = @"C:\hoge"; string input = Console.ReadLine(); //相対パスを受け取る //先頭が'\'でなければ付与 if (input.FirstOrDefault() != '\\') input = "\\" + input; string fullPath = Root + input;
こんなめんどくさいことをやらないといけなかったりする。
Path.Combine使えよ、と言われるかもしれないが、
これがまた微妙にクセのある動きをするのだ。
string full1 = Path.Combine(@"C:\MyDir", @"hoge"); string full2 = Path.Combine(@"C:\MyDir\", @"hoge"); string full3 = Path.Combine(@"C:\MyDir", @"\hoge"); string full4 = Path.Combine(@"C:", @"MyDir\hoge"); string full5 = Path.Combine(@"C:", @"\MyDir\hoge");
上の例はすべて「C:\MyDir\hoge」になってほしい気がするけども、
実はそうなるのは上の2つのみ。
full3は「\hoge」になってしまう。
さっきの話と同じで、先頭の区切り文字をルートとみなすからなんだろう。
full4はというと、これはそのままくっつけた「C:MyDir\hoge」になってしまう。
ドライブレターのあとの区切り文字は付与してくれない。
さらにやっかいなことに、じゃあ後ろにつけようと思ってfull5のように書くと、
これは先頭がルートとみなされて「\MyDir\hoge」が返ってくる。
という感じでいろいろ考えると、結局最初のように演算子で足しちゃうのが
一番楽なんじゃねえかという気がしてくる。
あとはWindows以外のことも考えるのであれば、区切り文字は
Path.DirectorySeparatorCharで見ておくほうがいいだろうというくらい。
もうちょっとだけつっこんで書くと、相対パスを指定するときに
「.\hoge」みたいなURI形式の階層表現をするユーザーもいたりするので、
それも対応したい場合は、System.Uriクラスのコンストラクタを使って
連結するという手もある。
Uri baseUri = new Uri(@"C:\MyDir\"); string full1 = new Uri(baseUri, @".\hoge").LocalPath; //"C:\MyDir\hoge" string full2 = new Uri(baseUri, @"..\hoge").LocalPath; //"C:\hoge"
この場合はbaseUriの末に区切り文字があるかないかで結果が変わるので注意。
以上。なげえ。