开发者

Single instance batch file?

开发者 https://www.devze.com 2022-12-16 22:56 出处:网络
:: dostuff.bat @echo off :: insert long-running process call here : End What can I add to this batch f开发者_高级运维ile to make it terminate if it\'s already running in another process when it\'s e
:: dostuff.bat
@echo off
:: insert long-running process call here
: End

What can I add to this batch f开发者_高级运维ile to make it terminate if it's already running in another process when it's executed?


Well, if there can be only exactly one instance ever, then you can probably do this by creating a dummy file somewhere, probably in the temp directory:

copy NUL "%TEMP%\dostuff_is_doing_stuff.tmp"

you then remove it after you're done:

del "%TEMP%\dostuff_is_doing_stuff.tmp"

and at the start of the batch file you can check whether that file exists and exit accordingly:

if exist "%TEMP%\dostuff_is_doing_stuff.tmp" (
    echo DoStuff is already running. Exiting ...
    exit /b 1
)

Similarly, you can do that by changing the window title, which should also be more robust against a Ctrl+C'ed or crashed batch file.

Set the title:

title DoStuff

Re-set it afterwards:

title Not doing stuff

Check for it:

tasklist /FI "WINDOWTITLE eq DoStuff"

However, this has one problem: When cmd runs as administrator you'll get "Administrator: DoStuff" as the title. Which doesn't match anymore with tasklist. You can hack-solve it by also checking for "Administrator: DoStuff" but that might look different depending on the Windows UI language, so it's probably not the best solution.


There is no way to do this in a clean way without using a custom external tool (You want something that creates a mutex AND runs (and waits for) your external command, this way, if the process is killed, the mutex dies with it)

Batch:

@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++ helper app:

//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;
}


You mostly can't in kind way.

First of because it is not trivial to do. You have to find the correct one cmd.exe process when in general it is not quite easy to "do all things right".

Second, termination may be is not enough to stop the script, because one script can run another script in cycle, so just terminating one will not lead to complete termination of another. More over, it can lead to a broken execution because you have to terminate the whole process tree from the root "atomically" to properly stop the execution.

Instead of "search to terminate" you can just "don't execute if already running". The technique is the same as in unix shell scripting: try to create some unique directory and lock it from removing by write redirection into a file in that directory.

i am using the script names something like 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

Usage:

exec_once_or_exit.bat <UniqueNameForLock> <PathToExecutable> <8Arguments>
exec_once_or_exit.bat <UniqueNameForLock> <BatchCommand(s)>

Explanation:

The mkdir command will return non zero exit code if directory already exists or can't be created for some reason, and 0 if created.

The LOCK_DIR_NAME_SUFFIX is used to have unique name for the file in the directory. Is uses all these replacements over specific characters because %DATE% and %TIME% variable's format depending on the format of date and time in the windows localization page.

Redirection over the stream id 9 is used to lock the file in directory for write and so locks the parent directory itself from removing. If the stream for some reason can't be redirected then the script terminates immediately by the cmd.exe w/o further execution which is enough for the mutual exclusion of execution. So, because the redirection happens before the execution there is no fear that something can execute before the redirection.

If crash or ctrl-c is happened then the directory will remain on the storage (hdd, ssd, that ever else), so to clean up it in that case the script tryes to remove the whole directory with all files inside before the mkdir.

The only thing what can happen here wrong is that the no one may acquire the execution which seems not so frequent to happen. At least it is better than "more than one at a time".


The best, simple, charming way to do it:

@echo off
set /a count=0
for /f "skip=3 USEBACKQ delims=" %%a in (`wmic process where "name='cmd.exe' and commandline like '%%%~nx0%%'" get processid`) do set /a count+=1
if %count% gtr 1 exit
echo 我要运行, Please run me....
pause


TWO FILES:

-START.BAT-

if exist "%TEMP%\dostuff_is_doing_stuff.tmp" (   
exit /b 1
)

copy NULL "%TEMP%\dostuff_is_doing_stuff.tmp"

cmd /K COMMANDS.bat

-COMMANDS.BAT-

REM ...do something

REM ...do something

REM ...do something    

DEL "%TEMP%\dostuff_is_doing_stuff.tmp"


Start->Run:

echo cmd /k -^|->-.bat&-

Violates the rules because (many) more than one instance runs simultaneously- but it's a fun way to get free food: bet your coworker lunch that he can't kill it without cold-booting.

0

精彩评论

暂无评论...
验证码 换一张
取 消