今回はVBAで重複しないファイル名を作成する方法について考えてみました。
きっかけ
ちょっと面白いネタだったので、今後の参考資料としてメモ
これは地味に難しい案件。
— ことりちゅん@えくせるちゅんちゅん (@KotorinChunChun) June 21, 2019
どの程度の頻度で保存するデータなのか、どの程度の人数が使うのかで変わってくる。
UserNameは結構衝突しやすい(会社による)
IP、MAC、GUIDは取得が面倒だし、
Do Loopの衝突回避は気持ち悪い。
そうだ!時分秒 & Application.Hwndなら衝突しずらいのでは!? https://t.co/qyQrHdh3uB
一部論外のものがあるものの、各種方法には一長一短があるので、状況に応じて使い分けると良い。
コード紹介
とりあえず確認用に作ってみたソースコード
カレントディレクトリにテキストデータを書き出したい場合は、カレントディレクトリをブックのパスへ
を実行すること
一部の情報の取得については関数化されているので、そのままコピペしても動かないが、検索すれば出てくるのでここでは記載しない。
Option Explicit Dim fso As New FileSystemObject Function GetFileName(Key As String) As String GetFileName = "hoge_" & Format(Now, "yyyymmddhhnnss") & "_" & Key & ".txt" End Function Sub CreateTestFile(Key As String) fso.CreateTextFile GetFileName(Key), False End Sub Sub カレントディレクトリをブックのパスへ() ChDir ThisWorkbook.Path End Sub Sub YMDHMS() Call CreateTestFile("") End Sub Sub Random() Randomize Call CreateTestFile(Int(Rnd * 1000)) End Sub Sub OfficeUserName() Call CreateTestFile(Application.UserName) End Sub Sub OfficeWindowHandle() Call CreateTestFile(Application.Hwnd) End Sub Sub NewworkUserName() Call CreateTestFile(CreateObject("WScript.Network").UserName) End Sub Sub GUID() Call CreateTestFile(GetGUID()) End Sub Sub IPAddress() Call CreateTestFile(Get_IPAddress()) End Sub Sub MacAddress() Call CreateTestFile(Replace(Get_MacAddress(), ":", "-")) End Sub Sub DoLoop() Do If Not fso.FileExists(GetFileName(Key)) Then fso.CreateTextFile GetFileName(Key), False End If Application.Wait [Now() + "00:00:00.5"] Loop End Sub
各種方法の説明
基本形(YYYYMMDDHHMMSS)
とりあえず重複を防ぐための方法として、一番楽なのが年月日時分秒を付ける方法。
この方法だと書き込み頻度が高い場合は衝突する危険性がある。
乱数
お手軽なのが日付に乱数を付与する方法
桁数を増やせば事故率が皆無となるまで精度を上げる事も可能。ある程度の桁数なら日付は必要ないが。
実行する度に値が変わるので、「再保存」に対応するにはスタックしておく必要があるのが厄介か。
当然、ファイルから保存者の特定もできない。(前回保存者から特定出来る場合もあるが)
Officeのユーザー名
Application.UserName
を使って、Officeの設定で決めたユーザー名を取得する方法
この設定は自由に変える事ができ、職場環境などによっては衝突することがままある。
- 社内統一、部内統一
- イニシャルのみの記載
- 内線番号の記載(この方法は、読み取り専用となった際に、とても、とても合理的だと思う)
ウィンドウハンドル
Application.Hwnd
を使って、現在起動中のExcelのウィンドウハンドルを取得する方法日付と組み合わせればまず衝突することはない。
衝突の起こりやすいマルチプロセスの際も別のハンドル値がつくので、安心してゴリゴリ回せる。
保存者の特定は別途ハンドル情報を書き出しておかないと難しい。(前回保存者からry)
PCのユーザー名
CreateObject("WScript.Network").UserName)
などを使って、PC/ネットワークユーザー名を取得する方法誰が保存したのか一目で分かるので、ユーザビリティに最も優れていると思う。
欠点は上記スペルを暗記する必要があることと、マルチプロセス非対応となる点か。
GUID
- APIを駆使して絶対に重複の発生し得ないGUIDを生成する方法
- こんなの誰も求めてない。
- ネタとしか言わざる負えない。
MACアドレス
- パソコンのLANアダプタのMACアドレス(製造番号的なにか)を使った方法
- これも誰も求めてない。
IPアドレス
- 同上につき以下略
後半のは
- 生成プログラムが長い
- 生成された文字列が長い
- 根本的に本件に適していない。
という感じなので、基本的には論外ですね。
Do~Loopで保存に成功するまで繰り返す
「絶対にエラーを起こさず保存したい」という話であれば、究極的に言えばこれしか無いでしょう。
エクスプローラで良くある(2)
や(3)
を付与していくような付け方をお求めなら、
が参考になるかもしれません。
各手法の比較
重複リスク | マルチプロセス | 保存者の特定 | 手軽さ | |
---|---|---|---|---|
日時 | 有 | × | × | ○ |
乱数4桁 | 1/10000 | ○ | × | ○ |
Officeのユーザー名 | 結構ある | × | △1 | ○ |
アプリケーションハンドル | 16711657通り | ○ | △2 | ○ |
PCのユーザー名 | 皆無 | × | ○ | △3 |
GUID | 皆無 | ○ | × | × |
MACアドレス | 皆無 | × | ○ | × |
IPアドレス | 皆無 | × | ○ | × |
Do~Loop | 無 | ○ | × | × |
△1 : ユーザー名が重複する環境では特定できない。
△2 : ウィンドウハンドルとユーザー名の対応表を記したログも合わせて書き出さないと特定出来ない。
△3 : 構文は短いので覚えちゃえばOK。
重複リスク
日時のみの場合に重複は「有」、Do~Loopで保存に成功するまで巡回する方法は「無」と決めて、その範囲内で重複リスクの度合いを適当に書いてみました。
ウィンドウハンドルは16711657通り
と書きましたが、偏りがあって1/16711657
とはならないだろうという予想から分数にはしていません。実態がどうなのかまでは分かりません。
マルチプロセス
こんな事をするのは稀だと思いますが、単一PCでマルチプロセスで並行処理している場合もあります。
そんな時はプロセスごとに異なる文字列を生成しないと、衝突してエラーになる恐れがあります。
保存者の特定
後から保存したのが誰か確認する必要がある場合は、確認可能な方法を使うべきでしょう。
例えば、同じ人間からの保存は最後の1つだけ。と言った場合。
例えば、同じプロセスで繰り返し上書き保存している場合は、古いファイルは消したい。と言った場合。
一方で特定されたくない場合。特定できる必要がない場合は、もっとお手軽な方法でも良さそうです。
既に述べているようにExcelブックなどであれば、「前回保存者」というファイルのプロパティから特定する手法もあるにはある。
まとめ
ただ単に重複を防ぐなら好きな方法でどうぞ。
ファイル名を見て誰か識別したい時は「ユーザー名」
なんでも良いから重複を回避させたい場合は「ウィンドウハンドル」
が楽なような気がします。
以上
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)