单实例批处理文件?

时间:2010-01-21 15:40:35

标签: windows batch-file single-instance

:: dostuff.bat
@echo off
:: insert long-running process call here
: End

如果批处理文件在执行时已经在另一个进程中运行,我可以添加到该批处理文件中以使其终止吗?

6 个答案:

答案 0 :(得分:8)

好吧,如果只有一个实例,那么你可以通过在某个地方创建一个虚拟文件来实现这一点,可能在临时目录中:

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 'ed或崩溃的批处理文件也应该更强大。

设置标题:

title DoStuff

之后重新设置:

title Not doing stuff

检查:

tasklist /FI "WINDOWTITLE eq DoStuff"

但是,这有一个问题:当cmd以管理员身份运行时,您将获得“Administrator:DoStuff”作为标题。哪个与tasklist不匹配。您也可以通过检查“Administrator:DoStuff”来解决它,但根据Windows UI语言可能会有所不同,因此它可能不是最佳解决方案。

答案 1 :(得分:3)

如果不使用自定义外部工具,则无法以干净的方式执行此操作(您需要创建互斥锁并运行(并等待)外部命令的内容,这样,如果进程被终止,则互斥锁与它一起死去)

批次:

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

答案 2 :(得分:1)

你绝大多数都不能善待。

首先是因为做起来并非易事。你必须找到正确的一个cmd.exe进程,一般来说,“做正确的事情”并不容易。

其次,终止可能不足以停止脚本,因为一个脚本可以循环运行另一个脚本,因此只需终止一个脚本就不会导致另一个脚本完全终止。此外,它可能导致执行中断,因为您必须从根“原子”终止整个进程树以正确停止执行。

不是“搜索终止”,而是“只要已经运行就不执行”。该技术与unix shell脚本中的技术相同:尝试创建一些唯一的目录,并通过写入重定向将其锁定到该目录中的文件中。

我使用的脚本名称类似于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。

唯一可能发生错误的是,没有人可能获得似乎不那么频繁发生的执行。至少它比“一次不止一个”更好。

答案 3 :(得分:0)

两个文件:

-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"

答案 4 :(得分:0)

开始 - &gt;运行:

echo cmd / k - ^ | - &gt; - 。bat&amp; -

违反规则,因为(很多)同时运行多个实例 - 但这是一种获得免费食物的有趣方式:打赌你的同事午餐,他不能在没有冷启动的情况下杀死它。

答案 5 :(得分:0)

做到这一点的最佳,简单,迷人的方式:

@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