nyagos には、ファイルをゴミ箱へ移動させる trash というコマンドがあります。
実装は Lua で、こんな感じで COM オブジェクトで実現してました。
nyagos.d/trash.lua :
if not nyagos then print("This is a script for nyagos not lua.exe") os.exit() end nyagos.alias.trash = function(args) if #args <= 0 then nyagos.writerr("Move files or directories to Windows Trashbox\n") nyagos.writerr("Usage: trash file(s)...\n") return end local fsObj = nyagos.create_object("Scripting.FileSystemObject") local shellApp = nyagos.create_object("Shell.Application") local trashBox = shellApp:NameSpace(nyagos.to_ole_integer(10)) args = nyagos.glob((table.unpack or unpack)(args)) for i=1,#args do if fsObj:FileExists(args[i]) or fsObj:FolderExists(args[i]) then trashBox:MoveHere(fsObj:GetAbsolutePathName(args[i])) else nyagos.writerr(args[i]..": such a file or directory not found.\n") end end trashBox:_release() shellApp:_release() fsObj:_release() end
この trash、削除したファイルを復元できるので、まちがったとき便利なのですが、nyagos 内蔵コマンドなので、バッチファイルなどからは使えません。
これを JScript化すると、こんな感じになります。
var shellApp = new ActiveXObject("Shell.Application"); var trashBox = shellApp.NameSpace(10); var fsObj = new ActiveXObject("Scripting.FileSystemObject"); for(var i=0 ; i <WScript.Arguments.length ; i++ ){ var arg1 = WScript.Arguments(i); var path1 = fsObj.GetAbsolutePathName(arg1); trashBox.MoveHere(path1); }
これをそのまま使ってもよいのですが、 VBScript が今後 Windows ではオプション機能になるという発表もあったため、JScript も今後継続して使えるか分かりません。
ということで、勉強がてら Rust で作ってみました。
当初は Lua/JScript 版と同様に COM を使うつもりだったのですが、Rust の場合、Go の go-ole ほどお手軽に使えるライブラリがなく、予想以上に難度が高かったので、API を直たたきするアプローチに切り替えました。
によると、SHFileOperationW という API で実現可能のようです。幸い Rust には既にそれにアクセスするためのパッケージも用意されていました。
- SHFileOperationW in windows::Win32::UI::Shell - Rust
- SHFILEOPSTRUCTW in windows::Win32::UI::Shell - Rust
ここまでくると、もう構造体 SHFILEOPSTRUCTW のフィールドに設定する値をうまいことでっちあげるだけです。Rust で NUL終端なUTF16テキストでパスのリストを作るのが少々めんどくさかったのですが、意外とさっくりと出来ました ( COM でなんとかしようと右往左往していた時間の方が圧倒的に長かった )
extern crate glob; use core::ffi::c_void; use windows::core::{w, PCWSTR}; use windows::Win32::Foundation::{BOOL, HWND}; use windows::Win32::UI::Shell::{ SHFileOperationW, FOF_ALLOWUNDO, FOF_NOCONFIRMATION, FO_DELETE, SHFILEOPSTRUCTW, }; fn append_fname(buffer: &mut Vec<u16>, fname: &str) { println!("{}", fname); let mut fname_vec: Vec<u16> = fname.encode_utf16().collect(); buffer.append(&mut fname_vec); buffer.push(0) } fn trash() -> Result<i32, Box<dyn std::error::Error>> { let mut source: Vec<u16> = Vec::new(); for fname in std::env::args().skip(1) { let mut glob_ok = false; for filename in glob::glob(&fname)? { if let Some(filename) = filename?.to_str() { append_fname(&mut source, &filename); glob_ok = true; } } if !glob_ok { append_fname(&mut source, &fname); } } if source.len() <= 0 { return Ok(0) } source.push(0); let mut sh_file_op_struct = SHFILEOPSTRUCTW { hwnd: HWND(0), wFunc: FO_DELETE, pFrom: PCWSTR(source.as_mut_ptr()), pTo: w!(""), fFlags: (FOF_ALLOWUNDO | FOF_NOCONFIRMATION) as u16, fAnyOperationsAborted: BOOL(0), hNameMappings: 0 as *mut c_void, lpszProgressTitle: w!("to trash"), }; unsafe { Ok(SHFileOperationW(&mut sh_file_op_struct)) } } fn main() { match trash(){ Err(err) => { eprintln!("{}", err); std::process::exit(1) } Ok(n) => std::process::exit(n), } }