はじめに
筆者の一連の記事の続きです。
PowerShell スクリプトとバッチファイルのどちらもたいていのディレクトリはうまく処理できるのですが,いずれも完璧ではありません。バッチファイルはディレクトリ名にパーセント記号 % が含まれると失敗します。一方,PowerShell はディレクトリ名に角括弧のペア [...] が含まれると失敗します。とくにカレントディレクトリに角括弧が含まれている場合,カレントディレクトリが PowerShell のシステムディレクトリに移動してしまうという致命的な危険性を孕んでいます。
いずれも文字列内の変数を自動展開してくれるというスクリプト言語特有の利点であり弱点に基づいています。要するに根本的な対策が非常に難しいと思われます。
ということで JavaScript(Windows Script Host)で作り直すことにしました。なお,下記記事の成果を反映してカラー表示対応にしました。
実装コード
JavaScript 版が下記になります。環境変数 WT_SESSION が定義されている場合は Windows Terminal から実行されているものと解釈,レジストリ HKCU\Console\VirtualTerminalLevel が 0 以外の値のときもエスケープシーケンスが利用可能と判断してカラー表示するようにしました。
var fs = WScript.CreateObject("Scripting.FileSystemObject");
var sh = WScript.CreateObject("WScript.Shell");
var args = WScript.Arguments.Unnamed
//------------------------------------------------------------------------------
// Windows Terminal から実行または HKCU\Console\VirtualTerminalLevel = 1 のとき
//------------------------------------------------------------------------------
var env = sh.Environment("Process");
var wt = (function() {
if(env("WT_SESSION") != "") return true;
try {
var level = sh.RegRead("HKCU\\Console\\VirtualTerminalLevel")
return level != 0 ? true : false;
} catch(e) {
return false;
}
})();
//------------------------------------------------------------------------------
// コンソール出力
//------------------------------------------------------------------------------
function output(s) {
WScript.StdErr.WriteLine(s);
}
function warning(s) {
output(wt ? "\x1b[33m" + s + "\x1b[0m" : s);
}
function alert(s) {
output(wt ? "\x1b[31m" + s + "\x1b[0m" : s);
}
//------------------------------------------------------------------------------
// ヘルプメッセージ
//------------------------------------------------------------------------------
if(args.Count == 0) {
output("ディレクトリ構造を最適化します。");
output("");
output("CHECKDIR(.JS) [ディレクトリ名] ...");
output("");
output("<基本ルール>");
output("・サブディレクトリ(1)の中身が空の場合,サブディレクトリ(1)を削除します。");
output("・サブディレクトリ(1)の中にサブディレクトリ(2)しか存在しない場合,");
output(" サブディレクトリ(2)の中身をサブディレクトリ(1)に移動して,");
output(" サブディレクトリ(2)を削除します。");
WScript.Quit(0);
}
//------------------------------------------------------------------------------
// メインループ
//------------------------------------------------------------------------------
for(var i = 0; i < args.Count; i++) {
var path = args(i);
if(fs.FolderExists(path)) {
if(CheckDirectory("", path))
warning("ディレクトリ " + path + " は空です。");
} else {
alert("ディレクトリ " + path + " は存在しません!");
WScript.Quit(1);
}
}
WScript.Quit(0);
//------------------------------------------------------------------------------
// ディレクトリを検査 ※再帰関数
//------------------------------------------------------------------------------
function CheckDirectory(parent, path) {
//--------------------------------------------------------------------------
// 親ディレクトリの更新
//--------------------------------------------------------------------------
parent = (parent == "") ? path : parent + "\\" + path;
//--------------------------------------------------------------------------
// ディレクトリに移動
//--------------------------------------------------------------------------
var orig = sh.CurrentDirectory;
output("ディレクトリ " + parent + " を検査します。");
sh.CurrentDirectory = fs.GetAbsolutePathName(path);
//--------------------------------------------------------------------------
// フォルダオブジェクトを生成
//--------------------------------------------------------------------------
var folder = fs.GetFolder(sh.CurrentDirectory);
//--------------------------------------------------------------------------
// 空のディレクトリは削除
//--------------------------------------------------------------------------
var dirs = new Enumerator(folder.SubFolders);
for(dirs.moveFirst(); !dirs.atEnd(); dirs.moveNext()) {
var dir = dirs.item();
if(CheckDirectory(parent, dir.Name)) {
alert("ディレクトリ " + parent + "\\" + dir.Name + " は空なので削除します。");
fs.DeleteFolder(dir.Name);
}
}
//--------------------------------------------------------------------------
// ファイル数がゼロかつディレクトリ数が1個の場合,ディレクトリの中身を移動
// ディレクトリ名の重複を避けるため,ディレクトリ名を変更
//--------------------------------------------------------------------------
if(folder.Files.Count == 0 && folder.SubFolders.Count == 1) {
var dirs = new Enumerator(folder.SubFolders);
for(dirs.moveFirst(); !dirs.atEnd(); dirs.moveNext()) {
var dir = dirs.item();
warning("ディレクトリ " + parent + "\\" + dir.Name + " は冗長なので中身を移動します。");
var temp = fs.GetFolder(dir.Name);
temp.Name = "__temp__";
if(temp.Files.Count > 0) fs.MoveFile("__temp__\\*", ".");
if(temp.SubFolders.Count > 0) fs.MoveFolder("__temp__\\*", ".");
fs.DeleteFolder("__temp__");
}
}
//--------------------------------------------------------------------------
// 元のディレクトリに戻る
//--------------------------------------------------------------------------
sh.CurrentDirectory = orig;
//--------------------------------------------------------------------------
// ファイルもディレクトリもない場合,true を返す
//--------------------------------------------------------------------------
return (folder.Files.Count == 0 && folder.SubFolders.Count == 0) ? true : false;
}
サンプルのディレクトリ構造
それでは確かめてみましょう。サンプル1~6は前回,前々回の記事と同じです。
サンプルの実行例
以下はすべて Windows Terminal で実行した場合です。
サンプル1は無事成功します。なお,削除した場合は赤色 #C50F1F,移動した場合は黄色 #C19C00 でカラー表示するようにしました。
サンプル2~6も成功します。オールクリアです。ようやくまともなものが作れたような気がします。
備考
-
バッチファイルも JavaScript (WHS) も古い処理系なので,ディレクトリ名に Shift-JIS で表記できない文字が含まれている場合は失敗するかもしれません。
-
ディレクトリの中に通常のファイルやディレクトリ以外のオブジェクトが存在する場合,たとえばハードリンクやシンボリックリンクなどが貼られていた場合も失敗するかもしれません。※未確認