今回はC#でフォルダやファイルの作成、移動、削除をしてみようと思います。
今回もUnityのMonoDevelopを使用してサンプルを作成していきます。
それは誤ったフォルダやファイルのパスを指定してしまうと、違うフォルダやファイルを削除してしまうという点です。
がんばって作ったファイルを削除してしまう可能性もあるので注意してください!
と、書いたのはわたくし自身がフォルダ、ファイルの操作のサンプルを入れていたフォルダの中身を削除してしまい、今まで作ったサンプルを全部消してしまったからです。(^_^;)
新たに作成する分には問題ないですけど、削除はきついですね・・・・(T_T)/~~~
フォルダとファイルの作成
それではフォルダとファイルの作成方法について見ていきましょう。
DirectoryのCreateDirectory、FileのCreateを使った作成
フォルダ、ファイルの作成はDirectory、FileクラスのCreateDirectoryメソッドやCreateメソッドを使用すると作成出来ます。
1 2 3 4 5 | Directory.CreateDirectory (フォルダを作成する場所); File.Creat(ファイルを作成する場所); File.CreateText(ファイルを作成する場所); |
UnityではApplication.dataPathでゲームデータのフォルダパス、Application.persistentDataPathで永続的なデータディレクトリのパスを取得出来ます。
Application.dataPathの場合UnityEditor上ではAssetsフォルダが取得出来、スタンドアロン形式ではゲームのフォルダを取得出来ます。
Application.persistentDataPathだとiOSやAndroid等のスマホのpublicなディレクトリを取得出来るので、こちらを使った方がいいのかもしれません。
そこら辺はUnityのスクリプトリファレンスに載っています。
それでは、実際にMonoDevelopにスクリプトを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | using UnityEngine; using System.Collections; using System.IO; using System; public class CreateFolderFile1 : MonoBehaviour { // Use this for initialization void Start () { Debug.Log (Application.dataPath); Debug.Log (Application.persistentDataPath); string folderPath1 = Path.Combine (Application.dataPath, @"Scripts\File\SubFolder1"); string folderPath2 = Path.Combine (Application.dataPath, @"Scripts\File\SubFolder2"); string folderPath3 = Path.Combine (folderPath2, @"SubSubFolder"); string filePath1 = Path.Combine (folderPath1, "data1.txt"); string filePath2 = Path.Combine (folderPath2, "data2.txt"); string filePath3 = Path.Combine (folderPath3, "data3.txt"); FileStream fs1 = null; StreamWriter fs2 = null; FileStream fs3 = null; try { Directory.CreateDirectory (folderPath1); Directory.CreateDirectory (folderPath2); Directory.CreateDirectory (folderPath3); fs1 = File.Create (filePath1); fs2 = File.CreateText (filePath2); fs3 = File.Create (filePath3); } catch(Exception e) { Debug.Log (e.Message); } finally { if (fs1 != null) { try { fs1.Dispose (); } catch(Exception e2) { Debug.Log (e2.Message); } } if (fs2 != null) { try { fs2.Dispose (); } catch (Exception e2) { Debug.Log (e2.Message); } } if (fs3 != null) { try { fs3.Dispose (); } catch (Exception e2) { Debug.Log (e2.Message); } } } } } |
↑の例ではまずフォルダ、ファイルへのパスをstring型の変数に代入しています。
Path.Combineは引数で指定したパスをうまい具合に考慮して連結してくれます。
例えば第1引数で階層記号/が最後についていなくて、第2引数でも階層記号がついていなければ付けてくれます。
Application.dataPathでは/記号を階層で返し、第2引数で指定したリテラル文字列で指定したフォルダの階層では\記号を使っていますが、連結しても問題は出ていません。
どちらの階層記号を使っても問題はなさそう?ですね。
サンプルではApplication.dataPathでAssetsフォルダまでの階層を取得し、それにScripts/File/SubFolder1とScripts/File/SubFolder2というフォルダを作成しています。
Scripts/Fileという部分はわたくしが作ったフォルダ、ファイル操作用のサンプルを入れておくフォルダの階層でその中にSubFolder1とSubFolder2を作成します。
SubFolder2の下の階層にSubSubFolderを作成します。
ご自分の環境に合わせてフォルダパスやファイルパスを変更してください。
ファイルの作成も同じようにファイルまでのパスを指定し作成する事が出来ます。
CreateTextはUTF-8 エンコードされたテキストの書き込み用になります。
このスクリプトをMain Camera等のゲームオブジェクトに取り付け実行すると、Assets→Scripts→Fileフォルダの中にSubFolder1、SubFolder2が作られ、
SubFolder1の中にdata1.txtファイル
SubFolder2の中にSubSubFolderフォルダとdata2.txtファイル
SubSubFolderフォルダの中にdata3.txtファイル
が作成されます。
UnityEditorで実行した場合はすぐにフォルダやファイルがエディター上に表示されないかもしれません。
UnityEditorを一旦最小化して隠した後、表示するとファイルが表示されます。
FileのOpenを使って作成
FileのOpenメソッドを使用するとファイルを開くだけでなく、ファイルが存在しない場合に作成する事も出来ます。
1 2 3 | File.Open("ファイルパス", ファイルモード, ファイルの開き方); |
と引数を設定して開きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using UnityEngine; using System.Collections; using System.IO; public class CreateFolderFile2 : MonoBehaviour { // Use this for initialization void Start () { string filePath1 = Application.dataPath + @"\Scripts\File\data4.txt"; FileStream file = null; try { file = File.Open (filePath1, FileMode.OpenOrCreate, FileAccess.ReadWrite); } catch (IOException e) { Debug.Log (e.Message); } finally { if (file != null) { try { file.Dispose (); } catch (IOException e2) { Debug.Log (e2.Message); } } } } } |
Assets→Scripts→Fileにdata4.txtファイルがあればそのファイルを開き、なければ新しく作成します。
FileのOpenクラスメソッドでは第1引数にファイルパス、第2引数にファイルモード、第3引数にファイルの読み書きについて指定します。
ファイルモードではファイルが存在するかどうかでファイルを作ったりするかどうかなどの指定をします。
ファイルの読み込みや書き込みの処理をする時は例外が発生する時もあるので、try-catch-finallyを使って例外処理をしています。
try内でファイル処理を行い、例外が発生したらcatch内を実行、finallyはtryの処理が通常通り出来た場合も例外が発生した時も実行します。
finallyではDisposeメソッドを呼び出し、ファイルのリソースを解放(ファイルアクセス手段を閉じる)しています。
なぜfinallyにDisposeがあるのかと言えば、ファイルアクセスに成功した時も失敗した時も必ずリソースを解放しなければずっとファイルを開いたままその後の処理に行ってしまう可能性もあるからです。
このスクリプトを実行するとAssets→Scripts→Fileにdata4.txtファイルが作成されます。
usingステートメントを使ったリソースの解放
ここまで作成したスクリプトはtry-catch-finallyを使ってリソースの解放を行っていました。
しかしリソースの解放をするだけで面倒くさい処理をfinallyの中に記述しています。
例外処理もファイルIOエラーを取得し、メッセージを表示しているだけなのでこれを排除し、usingステートメントを使ってもっと簡単に記述出来ます。
usingステートメントを使えるのはIDisposableインタフェースを継承しているクラスに限りますが、File.Openの戻り値はFileStreamクラスでこのクラスはStreamクラスを継承して作成されており、StreamクラスはIDisposableインタフェースを継承しているのでusingステートメントを使用する事が出来ます。
先ほどのCreateFolderFile2クラスを書き換えてCreateFolderFile3クラスを作成してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; using System.IO; public class CreateFolderfile3 : MonoBehaviour { // Use this for initialization void Start () { string filePath1 = Application.dataPath + @"\Scripts\File\data4.txt"; using(FileStream file = File.Open (filePath1, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { } } } |
usingの後に()を書き、その中にファイルストリームを取得している処理を書きます。
{}の中がファイルストリームを使ってファイルの中身を操作する処理を書いたりしますが、今のところまだ操作についてやっていないので何も書いていません。
usingステートメントを使用すると、例外が発生してもDisposeが実行されます。
ずいぶんシンプルになりますね。(^^)/
例外処理も加えるとしたら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using UnityEngine; using System.Collections; using System.IO; using System; public class CreateFolderfile3 : MonoBehaviour { // Use this for initialization void Start () { string filePath1 = Application.dataPath + @"\Scripts\File\data4.txt"; try { using(FileStream file = File.Open (filePath1, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { } } catch (Exception e) { Debug.Log(e.Message); } } } |
こんな感じでしょうか。
フォルダ、ファイルが存在するかどうか
ここまでの処理ではフォルダ、ファイルが存在するかどうか確認せずにファイルを作成しています。
ですが、ファイル操作をする時はそもそもフォルダやファイルが存在するのか?を調べてから処理をしたい事もあります。
そんな時はDirectoryクラス,FileクラスのExistsメソッドを使用します。
1 2 3 4 | Directory.Exists("フォルダのパス"); File.Exists("ファイルのパス"); |
↑のようにパスを引数に設定し、存在するかどうか調べる事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using UnityEngine; using System.Collections; using System.IO; public class ExistFolderFile : MonoBehaviour { // Use this for initialization void Start () { string folderPath = Application.dataPath + @"\Scripts\File"; string filePath = Application.dataPath + @"\Scripts\File\data4.txt"; if (!Directory.Exists (folderPath)) { Debug.Log (folderPath + "は存在しません。"); } if (!File.Exists (filePath)) { Debug.Log (filePath + "は存在しません。"); } } } |
↑のようにフォルダやファイルが存在するかどうか確認してからファイルを開いたり、操作したりすると例外が発生する以前に処理を実行しないように出来ます。
フォルダ、ファイルの移動と名前の変更
フォルダ、ファイルの移動と名前の変更はDirectoryクラス、FileクラスのMoveメソッドを使用します。
1 2 3 4 | Directory.Move("移動前のフォルダのパス", "移動先のフォルダのパス"); File.Move("移動前のファイルのパス", "移動先のファイルのパス"); |
移動前のフォルダと移動先のフォルダが同じ場合は移動ではなく名前が変更されます。
同じフォルダに移動し直したと考えれば同じですね。
以下のサンプルはファイルの有無を確認していない為、あらかじめいくつかのファイルが存在している必要があります。
CreateFolderFile1とCreateFolderFile2を実行して作成したフォルダとファイル、またAssets→Scripts→Fileの中にdata5.txtファイルを置いておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using UnityEngine; using System.Collections; using System.IO; using System; public class MoveFolderFile : MonoBehaviour { // Use this for initialization void Start () { string FromFolderPath1 = Application.dataPath + @"\Scripts\File\SubFolder2"; string ToFolderPath1 = Application.dataPath + @"\Scripts\File\SubFolder1\SubFolder2"; string FromFilePath1 = Application.dataPath + @"\Scripts\File\data4.txt"; string ToFilePath1 = Application.dataPath + @"\Scripts\File\SubFolder1\data4.txt"; string FromFilePath2 = Application.dataPath + @"\Scripts\File\data5.txt"; string ToFilePath2 = Application.dataPath + @"\Scripts\File\data6.txt"; try { Directory.Move (FromFolderPath1, ToFolderPath1); File.Move (FromFilePath1, ToFilePath1); File.Move (FromFilePath2, ToFilePath2); } catch(Exception e) { Debug.Log (e.Message); } } } |
↑の例では
Directory.Move (FromFolderPath1, ToFolderPath1)で、Scripts\File\SubFolder2をScripts\File\SubFolder1\SubFolder2に移動させ、
File.Move (FromFilePath1, ToFilePath1)で、Scripts\File\data4.txtをScripts\File\SubFolder1に移動させ、
File.Move (FromFilePath2, ToFilePath2)で、Scripts\File\data5.txtをdata6.txtという名前に変更しています。
別のフォルダを指定した場合は移動、同じフォルダで移動した場合は名前の変更となります。
フォルダ、ファイルの削除
最初の方でも書きましたが、削除する処理を実行する時はファイルパスをよく確認してから行ってください。
フォルダ、ファイルの削除をするにはDirectoryクラス、FileクラスのDeleteメソッドを使用します。
1 2 3 4 | Directory.Delete("フォルダのパス", 再帰的に子要素を削除するかのbool値); File.Delete("ファイルのパス"); |
Directory.Deleteの場合は第2引数にtrueを設定すると、指定したフォルダの子要素にフォルダやファイルがあった時にそれらも削除します。
フォルダ、ファイルの削除をする時に重要なのはそのフォルダ、ファイルが存在しているかどうか?とファイルリソースの解放がちゃんとされた後に削除しているか?です。
ファイルのリソースが解放されていない時に削除しようと思うと思わぬ不具合の原因になりますので、削除したファイルのリソースは解放するようにします。
今回作成するサンプルはUnityエディター上で実行すると自身のフォルダが残ってしまう為、スタンドアロン形式でビルドし確認する事にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | using UnityEngine; using System.Collections; using System.IO; using UnityEngine.UI; using System; public class DeleteFolderfFile : MonoBehaviour { public Text text; private string folderPath; private string subFolderPath; private string filePath; // Use this for initialization void Start () { folderPath = Path.Combine (Application.dataPath, @"Scripts/File/SubFolder2"); subFolderPath = Path.Combine (folderPath, "SubSubFolder"); filePath = Path.Combine (folderPath, "test.txt"); } void Update() { if (Input.GetButtonDown ("Fire1")) { text.text = ""; if (Directory.Exists (folderPath)) { // UnityEditor上だと子要素だけ削除 try { Directory.Delete (folderPath, recursive: true); } catch(Exception e) { text.text = e.Message; } text.text = folderPath + "を削除しました。"; } else { text.text = folderPath + "は存在しません。"; } } else if (Input.GetButtonDown ("Fire2")) { text.text = ""; text.text = "フォルダパス : " + folderPath; } else if (Input.GetButtonDown ("Fire3")) { text.text = ""; FileStream fs = null; try { Directory.CreateDirectory (subFolderPath); // Disposeしないとファイルの削除時に不具合が出る using (fs = File.Create (filePath)) { } } catch(Exception e) { text.text = e.Message; } text.text = subFolderPath + "と" + filePath + "にフォルダ、ファイルを作成しました。"; } } } |
publicでUnityのUIのテキストを設定出来るようにし、行った処理をそのテキストに出力するようにします。
Updateメソッドでマウスの左クリックでフォルダの削除、右クリックでフォルダのパスの表示、マウススクロールボタンを押す事でフォルダとその中にファイルを作成します。
ファイルを作成した後にusingを使ってリソースの解放をします。
ここで解放しないで作業を進めると不具合が発生します。
偉そうに言ってますが、わたくしフォルダを作成した後のリソースの解放をせずに、サンプルを実行してうまく動作せずかなり悩んでました・・・(^_^;)
Deleteメソッドの第2引数に名前付き引数で
recursive : true
と指定していますが、ここでtrueを設定することによって指定したパスのフォルダだけでなく子要素のフォルダとファイルの削除もする事が出来ます。
終わりに
フォルダやファイルクラスを使った操作方法は色々な方法があるので、C#リファレンスを確認してください。
今回使用したDirectoryクラスやFileクラスと似たようなDirectoryInfoクラスやFileInfoクラスを使う操作もあります。
DirectoryInfoやFileInfoはDirectoryとFileとほぼ同じ事が行えますが、インスタンス化して使用する必要があります。
ですが、DirectoryInfo、FileInfoでしか取得出来ない情報もあるのでそういった時はそちらを使う必要があります。
次回以降でファイルの中身を操作する処理をしてみたいと思います。