和風スパゲティのレシピ

日本語でコーディングするExcelVBA

既存ファイルを上書きしてファイルを移動する

移動先にファイルがあった場合は、上書きしてファイルを移動する方法を解説します。


ファイルを移動する2つの機能「Name」「FSO.MoveFile」は、
どちらも行先ファイルを上書きする機能を持っていません。


よって

  1. 行先のファイルがあればKill/FSO.DeleteFilleで削除して、
  2. そのあとName/FSO.MoveFileで移動する

という手順を踏む必要があります。

1つのファイルを上書きして移動する場合

対象が1ファイルの場合は、
FileSystemObject(以下FSO)を使わなくても実装できます。

FSOを使った方が柔軟な記述が出来ますが、
実行結果や実行速度にほとんど差はありません。

FileSystemObjectを使わないバージョン

Dim 移動するファイルのパス As String
移動するファイルのパス = "C:\Users\○○\Desktop\テスト1\Book1.xlsx"

Dim 移動先のファイルパス As String
移動先のファイルパス = "C:\Users\○○\Desktop\テスト2\Book1.xlsx"

' 移動先にファイルがあれば削除
If Dir(移動先のファイルパス) <> "" Then
    Kill 移動先のファイルパス
End If

Name 移動するファイルのパス As 移動先のファイルパス

 
FSOを使用しない場合は、

  • Dir関数で既存ファイルがあるか判定
  • Killステートメントで既存ファイルを削除
  • Nameステートメントでファイルを移動

という手順になります。

同じパスが何度も出てきますので、
混乱しないよう丁寧な名前の変数を使いましょう。

FileSystemObjectを使うバージョン

Dim FSO As New FileSystemObject

Dim 移動するファイルのパス As String
移動するファイルのパス = "C:\Users\○○\Desktop\テスト1\Book1.xlsx"

Dim 移動先のファイルパス As String
移動先のファイルパス = "C:\Users\○○\Desktop\テスト2\Book1.xlsx"

' 移動先にファイルがあれば削除
If FSO.FileExists(移動先のファイルパス) Then
    FSO.DeleteFile 移動先のファイルパス
End If

FSO.MoveFile 移動するファイルのパス, 移動先のファイルパス

 
FSOを使用する場合は、

  • FileExistsメソッドで既存ファイルがあるか判定
  • DeleteFileメソッドで既存ファイルを削除
  • MoveFileメソッドでファイルを移動

という手順になります。


上記のように2つのパスをどちらもファイルのフルパスで扱う場合は、
FSOを使わないバージョンと全く同じ文構造になります。


FSOを使うメリットは記述の柔軟性で、以下のように書くことも可能です。

Dim FSO As New FileSystemObject

Dim 移動するファイル As File
Set 移動するファイル = FSO.GetFile("C:\Users\○○\Desktop\テスト1\Book1.xlsx")

Dim 移動先のフォルダ As Folder
Set 移動先のフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\テスト2")

' 移動先にファイルがあれば削除
If FSO.FileExists(移動先のフォルダ.Path & "\" & 移動するファイル.Name) Then
    FSO.DeleteFile 移動先のフォルダ.Path & "\" & 移動するファイル.Name
End If

移動するファイル.Move 移動先のフォルダ.Path & "\"

 
このコードでは、

  • FileオブジェクトとFolderオブジェクトを活用(.Name/.Pathが使える)
  • MoveFileではなくファイル.Moveで移動を実行
  • 行先のファイル名を省略(フォルダパスを指定)すれば同名の移動になる

あたりの機能を使っています。


複雑なファイル処理が必要な場合は、
FSOの方が楽に書けることも多いと思いますので使い分けてください。


FSOについての詳細はこちらの記事一覧からどうぞ
www.limecode.jp

汎用関数化

この処理をよく行う方は、関数化しておくと便利です。

Public FSO As New FileSystemObject

' ファイル上書き移動の汎用関数
Sub 既存ファイルは上書きしてファイルを移動する _
    (移動するファイルのパス As String, 移動先のファイルパス As String)

    If FSO.FileExists(移動先のファイルパス) Then
        FSO.DeleteFile 移動先のファイルパス
    End If
    FSO.MoveFile 移動するファイルのパス, 移動先のファイルパス

End Sub
' 実行例
Call 既存ファイルは上書きしてファイルを移動する( _
    "C:\Users\○○\Desktop\テスト1\Book1.xlsx", _
    "C:\Users\○○\Desktop\テスト2\Book1.xlsx")

 
中身は上で紹介したコードそのままという簡単設計の関数ですが、
実行例の通りCall1回でこの処理が行えるようになります。


ファイル操作は同じパスを何度も使うことが多いため、
汎用関数にするメリットが大きいです。

簡単な関数ほど、積極的に作っていきましょう。


ついでですが、Subプロシージャを分ける場合は、

Public FSO As New FileSystemObject

この宣言でFSOを全Subで使いまわせます。

これも便利なので覚えておいてください。
www.limecode.jp

複数のファイルを一括で移動する場合

MoveFileメソッドはワイルドカード*を使った一括移動ができますが、
残念ながら同名の上書き判定を行うことができません。


DeleteFileメソッドもワイルドカードが使えるので、
一見同じ判定文(*.xlsx)でDeleteとMoveを実行すればいけそうです。

' これでいけそう?
FSO.DeleteFile "C:\Users\○○\Desktop\テスト2\*.xlsx"
FSO.MoveFile "C:\Users\○○\Desktop\テスト1\*.xlsx"
                    "C:\Users\○○\Desktop\テスト2\

 
ですが、これだと「同名でないテスト2のxlsxファイル」も削除されてしまいます。


この対策には2つのやり方があります。

CopyFileメソッドを利用する

CopyFileメソッドには第3引数「OverWriteFiles」があり、
これをTrueにする(省略時はTrueなので省略してもよい)ことで上書きコピーができます。

これとDeleteFileメソッドを組み合わせて、

  1. まずは複数のファイルを上書きコピー
  2. 完了後にコピーに使ったファイルを削除

とすることで疑似的に移動を実行できます。


コードはとても単純で、

FSO.CopyFile "C:\Users\○○\Desktop\テスト1\*.xlsx"
                    "C:\Users\○○\Desktop\テスト2\"
FSO.DeleteFile "C:\Users\○○\Desktop\テスト1\*.xlsx"

これで移動が完了します。


ただDeleteは取り返しのつかない処理なので、
パスの変え忘れなどに備え変数にしておきましょう。

Dim 移動ファイル判定パス As String
移動ファイル判定パス = "C:\Users\○○\Desktop\テスト1\*.xlsx"
Dim 移動先フォルダパス As String
移動先フォルダパス = "C:\Users\○○\Desktop\テスト2\"

FSO.CopyFile 移動ファイル判定パス, 移動先フォルダパス
FSO.DeleteFile 移動ファイル判定パス

変数化をちゃんとやっておかないと、
Copyの引数を変えてDeleteの引数を変え忘れたとき悲劇が起こります。

すべてのファイルをループして上書き移動する

上記のCopyFileメソッドを利用した方法は簡単なのですが、
少々トリッキーなため微妙な仕様変更ができません。


上書き・スキップの判定を各ファイルで行う必要がある場合は、
For Eachを素直に使った以下のコードで実装してください。

Dim FSO As New FileSystemObject

Dim 移動元フォルダ As Folder
Set 移動元フォルダ = FSO.GetFolder("C:\Users\○○\Desktop\テスト1")
Dim 移動先フォルダ As Folder
Set 移動先フォルダ = FSO.GetFolder("C:\Users\○○\Desktop\テスト2")

' 移動元フォルダのすべてのファイルをループ
Dim 移動候補 As File
For Each 移動候補 In 移動元フォルダ.Files

    ' 条件に合致するファイルか判定
    If 移動候補.Name Like "*.xlsx" Then
        
        ' 行先フォルダ内に同名ファイルがあれば削除
        If FSO.FileExists(移動先フォルダ.Path & "\" & 移動候補.Name) Then
            FSO.DeleteFile 移動先フォルダ.Path & "\" & 移動候補.Name
        End If

        ' 移動を実行
        移動候補.Move 移動先フォルダ.Path & "\"
        
    End If
Next

すこし長いですがかなり素直なコードなため、
FSOのForEachを書ける方であれば解説なしで読めると思います。


FSOのFor Each文の基本についてはこちらを参照ください。
www.limecode.jp


なお、この処理はDir関数でも出来そうに見えますが必ずFSOが必要です。

なぜならDir関数によるループ中は別のDir関数が使えないからです。


今回の処理では、全ファイルをループしている途中で、
行先のフォルダに同名ファイルがあるかを調べますからね。

どうしてもDirの中でDirを使う必要が出るのですが、
これをやると第1Dirのループが誤作動を起こします。


こちらの記事で解説していますので興味があればご覧ください。
www.limecode.jp