はじめに
Windows でもコマンドプロンプトを愛用しているが,ファイルを削除するときについ勢いで DEL *
と打ち込んでしまって後悔することが多々ある。かつてはファイルをゴミ箱に送る WSH スクリプトを自作して愛用していたが,ワイルドカードの処理に納得できず悩んでいた。
そうだ,C 言語で作ろう!シェル API を呼び出すだけなのでささっと作れるはずだ!
基本方針
Windows のシェル API である SHFileOperation を使う。詳しい仕様は下記参照されたい。
SHFileOperationW関数(shellapi.h) - learn.microsoft.com
シェル API の仕様
ファイルをゴミ箱に送るためには sfo.wFunc = FO_DELETE
とした上で sfo.fFlags
に FOF_ALLOWUNDO
属性を付加しなくてはならない。コンソールプログラムなので余計な進行状況ダイアログの表示を抑制したい人はさらに FOF_SILENT
属性を追加しても良いだろう。あるいは FOF_NO_UI
属性でも良いかもしれない。
sfo.pFrom
にはファイル名またはフォルダ名のリストを格納したバッファのポインタを指定する。注意事項として以下の点に留意しなくてはならない。
- フルパス名であること。
- ワイルドカードが使える,ただしファイル名の位置のみで使える。
- 複数のファイル名またはフォルダ名を指定できる。区切り文字はヌル文字(\0)とする。
- バッファの末尾にはヌル文字(\0)を二つ連続する。
SHFILEOPSTRUCT sfo = { 0 };
sfo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
sfo.wFunc = FO_DELETE;
sfo.pFrom = buf;
SHFileOperation( &sfo );
ファイル名・フォルダ名の一覧作成
コマンドライン引数,すなわち argv[]
にファイル名・フォルダ名が格納されているとする。存在しないファイル・フォルダが含まれているとシェル API の実行そのものが失敗してしまうようだ。存在するファイルやフォルダのみが削除されるという訳ではない。ファイル名やフォルダ名の指定にはワイルドカードが含まれている可能性があるので _findfirst
を用いて存在チェックを行う。同時にフルパス名に変換した場合の文字列の長さを得る。ヌル文字(\0)の分も漏れずにカウントする必要がある。
struct _wfinddata_t info;
wchar_t path[MAX_PATH];
int len = 0;
for( int i = 1; i < argc; i++ ) {
intptr_t handle = _wfindfirst( argv[i], &info );
if( handle == -1 ) {
fwprintf( stderr, L"%s は存在しません!!\n", argv[i] );
return (wchar_t*)NULL;
}
_findclose( handle );
_wfullpath( path, argv[i], MAX_PATH );
len += wcslen( path );
len++;
}
len++;
wchar_t *buf = (wchar_t*)malloc( sizeof(wchar_t) * len );
if( buf == (wchar_t*)NULL ) {
fputws( L"メモリの確保に失敗しました!!\n", stderr );
return (wchar_t*)NULL;
}
wchar_t *p = buf;
for( int i = 1; i < argc; i++ ) {
_wfullpath( path, argv[i], MAX_PATH );
p = wcscpy2( p, path );
*p++ = L'\0';
}
*p++ = L'\0';
文字列のコピーには愛用している自作のコピー関数を使用する。この関数はコピー先の文字列の末尾のヌル文字(\0)を示すポインタを返すので,連続して使用することで文字列連結するのに適しているのだ。
wchar_t *wcscpy2( wchar_t *s1, wchar_t *s2 ) {
while( ( *s1 = *s2 ) != L'\0' ) {
s1++;
s2++;
}
return( s1 );
}
実装コード
実装コードを以下に示す。なお,本プログラムはVisual Studio 2022 Community Edition にてコンパイルした。
#define UNICODE
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <io.h>
#pragma comment( lib, "shell32" )
//------------------------------------------------------------------------------
// 文字列 s2 を文字列 s1 にコピーする
// 文字列 s1 の末尾(ヌル文字)を示すポインタを返す
//------------------------------------------------------------------------------
static wchar_t *wcscpy2( wchar_t *s1, wchar_t *s2 ) {
while( ( *s1 = *s2 ) != L'\0' ) {
s1++;
s2++;
}
return( s1 );
}
//------------------------------------------------------------------------------
// ファイル・フォルダ一覧を作成する
// ・ファイル・フォルダが存在しない場合はヌルポインタを返す
// ・ファイル・フォルダ名をフルパスに変換する
// ・区切り文字はヌル文字(\0)とし,最後はヌル文字(\0)を二連続とする
//------------------------------------------------------------------------------
static wchar_t *create_list( int argc, wchar_t *argv[] ) {
//--------------------------------------------------------------------------
// ファイル・フォルダの存在チェック&バッファサイズを得る
//--------------------------------------------------------------------------
struct _wfinddata_t info;
wchar_t path[MAX_PATH];
int len = 0;
for( int i = 1; i < argc; i++ ) {
//----------------------------------------------------------------------
// ワイルドカードを含むパス名の存在チェック
//----------------------------------------------------------------------
intptr_t handle = _wfindfirst( argv[i], &info );
if( handle == -1 ) {
fwprintf( stderr, L"%s は存在しません!!\n", argv[i] );
return (wchar_t*)NULL;
}
_findclose( handle );
//----------------------------------------------------------------------
// フルパス名の長さを得る
//----------------------------------------------------------------------
_wfullpath( path, argv[i], MAX_PATH );
len += wcslen( path );
len++;
}
len++;
//--------------------------------------------------------------------------
// バッファを確保する
//--------------------------------------------------------------------------
wchar_t *buf = (wchar_t*)malloc( sizeof(wchar_t) * len );
if( buf == (wchar_t*)NULL ) {
fputws( L"メモリの確保に失敗しました!!\n", stderr );
return (wchar_t*)NULL;
}
//--------------------------------------------------------------------------
// ファイル・フォルダ名一覧をフルパスに変換して作成する
//--------------------------------------------------------------------------
wchar_t *p = buf;
for( int i = 1; i < argc; i++ ) {
_wfullpath( path, argv[i], MAX_PATH );
p = wcscpy2( p, path );
*p++ = L'\0';
}
*p++ = L'\0';
return( buf );
}
//------------------------------------------------------------------------------
// ファイル・フォルダをごみ箱に移動する
//------------------------------------------------------------------------------
static int remove_list( wchar_t *buf ) {
SHFILEOPSTRUCT sfo = { 0 };
sfo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
sfo.wFunc = FO_DELETE;
sfo.pFrom = buf;
return SHFileOperation( &sfo );
}
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
int wmain( int argc, wchar_t *argv[] ) {
//--------------------------------------------------------------------------
// ロケールの初期化
//--------------------------------------------------------------------------
setlocale( LC_ALL, "" );
//--------------------------------------------------------------------------
// ヘルプメッセージ
//--------------------------------------------------------------------------
if( argc < 2 ) {
fputws( L"ファイル(複数可)をごみ箱に移動します。\n", stderr );
fputws( L"\n", stderr );
fputws( L"REMOVE(.EXE) [ファイル名] ...\n", stderr );
fputws( L"\n", stderr );
fputws( L"ファイルまたはフォルダ(複数可)の一覧を指定します。\n", stderr );
fputws( L"ファイルまたはフォルダの指定にはワイルドカードを使用できます。\n", stderr );
fputws( L"フォルダを指定すると、フォルダ内の全てのファイルを削除します。\n", stderr );
return( -1 );
}
//--------------------------------------------------------------------------
// ファイル・フォルダ一覧の作成
//--------------------------------------------------------------------------
wchar_t *buf = create_list( argc, argv );
//--------------------------------------------------------------------------
// ファイル・フォルダをごみ箱に移動する
//--------------------------------------------------------------------------
int ret = remove_list( buf );
if( ret != 0 )
fputws( L"ファイル・フォルダの削除に失敗しました!!\n", stderr );
//--------------------------------------------------------------------------
// バッファの解放
//--------------------------------------------------------------------------
free( buf );
return( ret );
}
実行画面
引数なしで実行すると下記のようなヘルプメッセージが表示される。
ファイル(複数可)をごみ箱に移動します。
REMOVE(.EXE) [ファイル名] ...
ファイルまたはフォルダ(複数可)の一覧を指定します。
ファイルまたはフォルダの指定にはワイルドカードを使用できます。
フォルダを指定すると、フォルダ内の全てのファイルを削除します。
参考文献
Windowsで,簡単にファイルを「ごみ箱」に送るバッチのサンプルコード。削除処理に「シェル名前空間」を使う仕組みの解説 - はてなブログ