:: dostuff.bat
@echo off
:: insert long-running process call here
: End
実行時に別のプロセスで既に実行されている場合、このバッチ ファイルに何を追加して終了させることができますか?
:: dostuff.bat
@echo off
:: insert long-running process call here
: End
実行時に別のプロセスで既に実行されている場合、このバッチ ファイルに何を追加して終了させることができますか?
インスタンスが 1 つしか存在しない場合は、おそらく一時ディレクトリにダミー ファイルを作成することで、これを行うことができます。
copy NUL "%TEMP%\dostuff_is_doing_stuff.tmp"
完了したら削除します。
del "%TEMP%\dostuff_is_doing_stuff.tmp"
バッチファイルの開始時に、そのファイルが存在するかどうかを確認し、それに応じて終了できます。
if exist "%TEMP%\dostuff_is_doing_stuff.tmp" (
echo DoStuff is already running. Exiting ...
exit /b 1
)
Ctrl同様に、ウィンドウのタイトルを変更することでこれを行うことができます。これは、 +された、Cまたはクラッシュしたバッチ ファイルに対してもより堅牢になるはずです。
タイトルを設定します。
title DoStuff
後で再設定します。
title Not doing stuff
それを確認してください:
tasklist /FI "WINDOWTITLE eq DoStuff"
ただし、これには 1 つの問題があります。cmd を管理者として実行すると、タイトルとして「Administrator: DoStuff」が表示されます。とはもう一致しませんtasklist
。「Administrator: DoStuff」もチェックすることでハック解決できますが、Windows UI 言語によっては見た目が異なる可能性があるため、おそらく最善の解決策ではありません。
カスタム外部ツールを使用せずにこれをクリーンな方法で行う方法はありません (ミューテックスを作成し、外部コマンドを実行 (および待機) するものが必要です。このように、プロセスが強制終了された場合、ミューテックスはそれで終了します) )
バッチ:
@echo off
echo starting long running process
REM calling dir here just to get a long running operation
onecmd.exe cmd.exe /C dir /S /B \*
echo done...bye
C++ ヘルパー アプリ:
//Minimal error checking in this sample ;)
#include <Windows.h>
#include <TCHAR.h>
int main()
{
const TCHAR myguid[]=_T("50D6C9DA-8135-42de-ACFE-EC223C214DD7");
PROCESS_INFORMATION pi;
STARTUPINFO si={sizeof(STARTUPINFO)};
HANDLE mutex=CreateMutex(NULL,true,myguid);
DWORD gle=GetLastError();
if (!mutex || gle==ERROR_ALREADY_EXISTS)
{
CloseHandle(mutex);
return gle;
}
TCHAR*p=GetCommandLine();
if (*p=='"' && *(++p)) {
while (*p && *p!='"')++p;if (*p)++p;
}
else
while (*p && *p!=' ')++p;
while(*p==' ')++p;
if (CreateProcess(0,p,0,0,false,0,0,0,&si,&pi))
{
DWORD ec;
WaitForSingleObject(pi.hProcess,INFINITE);
GetExitCodeProcess(pi.hProcess,&ec);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return ec;
}
gle=GetLastError();
CloseHandle(mutex);
return gle;
}
ほとんどの場合、親切な方法ではできません。
まず、それは簡単なことではないからです。一般に、「すべてを正しく行う」ことが非常に簡単ではない場合、正しい 1 つの cmd.exe プロセスを見つける必要があります。
第 2 に、スクリプトを終了するだけではスクリプトを停止するには不十分な場合があります。これは、あるスクリプトが別のスクリプトを繰り返し実行できるため、あるスクリプトを終了するだけでは別のスクリプトが完全に終了するわけではないためです。さらに、実行を適切に停止するには、プロセス ツリー全体をルートから「アトミックに」終了する必要があるため、実行が中断される可能性があります。
「検索して終了する」の代わりに、「すでに実行中の場合は実行しない」ことができます。この手法は、UNIX シェル スクリプトの場合と同じです。一意のディレクトリを作成し、そのディレクトリ内のファイルへの書き込みリダイレクトによって削除しないようにロックします。
exec_once_or_exit.bat のようなスクリプト名を使用しています。
@echo off
setlocal
set "LOCK_DIR_NAME_SUFFIX=%DATE%_%TIME%"
set "LOCK_DIR_NAME_SUFFIX=%LOCK_DIR_NAME_SUFFIX::=_%"
set "LOCK_DIR_NAME_SUFFIX=%LOCK_DIR_NAME_SUFFIX:/=_%"
set "LOCK_DIR_NAME_SUFFIX=%LOCK_DIR_NAME_SUFFIX:-=_%"
set "LOCK_DIR_NAME_SUFFIX=%LOCK_DIR_NAME_SUFFIX:.=_%"
set "LOCK_DIR_NAME_SUFFIX=%LOCK_DIR_NAME_SUFFIX:,=_%"
set LASTERROR=0
rem cleanup if leaked by crash or ctrl-c, won't be removed if already acquired because of write redirection lock
rmdir /S /Q "%TEMP%\lock_%~1" >nul 2>&1
mkdir "%TEMP%\lock_%~1" && (
rem IMPL to use "goto :EOF" in next command instead of "exit /b %ERRORLEVEL%" under "block" command - "( )"
call :IMPL %%*
goto :EOF
)
exit /b -1024
:IMPL
rem call IMPL2 to recover exit code from commands like "exit /b"
call :IMPL2 %%* 9> "%TEMP%\lock_%~1\lock0_%LOCK_DIR_NAME_SUFFIX%.txt"
set LASTERROR=%ERRORLEVEL%
rmdir /S /Q "%TEMP%\lock_%~1"
exit /b %LASTERROR%
:IMPL2
rem a batch script must be always call over the "call" prefix
if "%~n2" == "bat" call %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9 && goto :EOF
if not "%~n2" == "bat" (
%2 %3 %4 %5 %6 %7 %8 %9
)
goto :EOF
使用法:
exec_once_or_exit.bat <UniqueNameForLock> <PathToExecutable> <8Arguments>
exec_once_or_exit.bat <UniqueNameForLock> <BatchCommand(s)>
説明:
mkdir コマンドは、ディレクトリが既に存在するか何らかの理由で作成できない場合はゼロ以外の終了コードを返し、作成された場合は 0 を返します。
LOCK_DIR_NAME_SUFFIX は、ディレクトリ内のファイルに一意の名前を付けるために使用されます。%DATE% および %TIME% 変数の形式は、Windows ローカリゼーション ページの日付と時刻の形式に依存するため、特定の文字に対してこれらすべての置換を使用します。
ストリーム ID 9 を介したリダイレクトは、ディレクトリ内のファイルを書き込み用にロックするために使用されるため、親ディレクトリ自体が削除されないようにロックされます。何らかの理由でストリームをリダイレクトできない場合、スクリプトは cmd.exe によってすぐに終了し、さらに実行する必要はありません。これは実行の相互排除に十分です。したがって、リダイレクトは実行前に行われるため、リダイレクトの前に何かが実行される心配はありません。
クラッシュまたは ctrl-c が発生した場合、ディレクトリはストレージ (hdd、ssd、その他) に残るため、その場合にクリーンアップするために、スクリプトは mkdir の前にあるすべてのファイルを含むディレクトリ全体を削除しようとします。
ここで起こりうる唯一の間違いは、それほど頻繁には起こらないと思われる実行を誰も取得できないことです。少なくとも「一度に複数」よりはましです。