Windows 单实例批处理文件?

Windows 单实例批处理文件?,windows,batch-file,single-instance,Windows,Batch File,Single Instance,如果批处理文件在执行时已经在另一个进程中运行,我可以向该批处理文件添加什么使其终止?好吧,如果只有一个实例,那么您可以通过在某个地方创建一个虚拟文件来完成,可能在temp目录中: :: dostuff.bat @echo off :: insert long-running process call here : End 然后在完成后将其删除: copy NUL "%TEMP%\dostuff_is_doing_stuff.tmp" 在批处理文件的开头,您可以检查该文件是否存在并相应退出:

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

好吧,如果只有一个实例,那么您可以通过在某个地方创建一个虚拟文件来完成,可能在temp目录中:

:: dostuff.bat
@echo off
:: insert long-running process call here
: End
然后在完成后将其删除:

copy NUL "%TEMP%\dostuff_is_doing_stuff.tmp"
在批处理文件的开头,您可以检查该文件是否存在并相应退出:

del "%TEMP%\dostuff_is_doing_stuff.tmp"
类似地,您可以通过更改窗口标题来实现这一点,对于Ctrl+C'ed或崩溃的批处理文件,窗口标题也应该更加健壮

设置标题:

if exist "%TEMP%\dostuff_is_doing_stuff.tmp" (
    echo DoStuff is already running. Exiting ...
    exit /b 1
)
之后重新设置:

title DoStuff
检查它:

title Not doing stuff

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

如果不使用自定义外部工具,则无法以干净的方式完成此操作(您需要创建互斥对象并运行(并等待)的工具)您的外部命令,这样,如果进程被终止,互斥锁也随之终止)

批次:

tasklist /FI "WINDOWTITLE eq DoStuff"
C++助手应用程序:

@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
//检查此示例中的最小错误;)
#包括
#包括
int main()
{
const TCHAR myguid[]=“50D6C9DA-8135-42de-ACFE-EC223C214DD7”);
处理信息;
STARTUPINFO si={sizeof(STARTUPINFO)};
HANDLE mutex=CreateMutex(NULL,true,myguid);
DWORD gle=GetLastError();
如果(!mutex | | gle==错误_已存在)
{
CloseHandle(互斥);
返回gle;
}
TCHAR*p=GetCommandLine();
如果(*p=='“&&&*(++p)){
while(*p&&*p!=“”)++p;if(*p)++p;
}
其他的
而(*p&&*p!='')+p;
而(*p='')++p;
if(CreateProcess(0,p,0,0,false,0,0,0,&si,&pi))
{
德沃德欧共体;
WaitForSingleObject(pi.hProcess,无限);
GetExitCodeProcess(pi.hProcess,&ec);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
返回ec;
}
gle=GetLastError();
CloseHandle(互斥);
返回gle;
}
两个文件:

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

-命令.蝙蝠-

-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
开始->运行:

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


违反了规则,因为(许多)多个实例同时运行-但这是一种获得免费食物的有趣方式:打赌你的同事午餐,如果没有冷启动,他无法杀死它。

你基本上不能以实物方式

首先,因为这不是小事。您必须找到正确的一个cmd.exe进程,但一般来说,“把所有事情都做好”并不容易

第二,终止可能不足以停止脚本,因为一个脚本可以循环运行另一个脚本,所以仅仅终止一个脚本不会导致另一个脚本的完全终止。此外,它还可能导致执行中断,因为您必须从根“原子地”终止整个流程树以正确停止执行

与“搜索终止”不同,您只需“如果已经运行,则不执行”。该技术与unix shell脚本中的技术相同:尝试创建一些唯一的目录,并通过将重定向写入该目录中的文件来锁定其删除

我使用的脚本名称类似于exec_once_或_exit.bat:

REM ...do something

REM ...do something

REM ...do something    

DEL "%TEMP%\dostuff_is_doing_stuff.tmp"
用法:

@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\u once\u或exit.bat
exec_once_或_exit.bat
说明:

如果目录已经存在或由于某种原因无法创建,mkdir命令将返回非零退出代码,如果创建,则返回0

LOCK_DIR_NAME_后缀用于为目录中的文件指定唯一的名称。Is对特定字符使用所有这些替换,因为%DATE%和%TIME%变量的格式取决于windows本地化页面中的日期和时间格式

流id 9上的重定向用于锁定目录中的文件以进行写入,从而锁定父目录本身以防止删除。如果由于某种原因无法重定向流,则脚本会立即终止cmd.exe,而不执行进一步的执行,这足以相互排除执行。因此,由于重定向发生在执行之前,因此不必担心会在重定向之前执行某些内容

如果发生崩溃或ctrl-c,则目录将保留在存储器(hdd、ssd等)上,因此在这种情况下,脚本会尝试删除整个目录以及mkdir之前的所有文件


这里唯一可能发生的错误是,没有人可能获得似乎不太频繁发生的死刑。至少它比“一次不止一个”要好。

最好的、简单的、迷人的方式:

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

任务列表的想法很棒。非常感谢。如何针对该命令的输出编写测试?也就是说,当我在批处理文件中运行tasklist时,有什么逻辑可以知道它是否告诉我批处理文件已经在运行?嗯,对于tasklist的内容,很抱歉。我以为任务列表会用一个退出代码来响应;然后,您可以将它们与
if errorlevel…
一起使用,但无论是否找到窗口,tasklist始终返回0。那有点糟糕。您仍然可以使用
for/f
捕获输出,但我建议不要这样做,因为这本身就很脆弱(本地化和其他东西)。对。当那不起作用时,我提出了我的问题。我可以将tasklist的结果导入findstr,findstr返回一个条件errorlevel。我不喜欢它,但当我创作批处理文件(特别是供个人使用,如本例中)时,我从不关心美学。啊,对了,
findstr
此刻让我难以捉摸。是的,那会更容易。不过,我并不特别喜欢依赖特定语言或格式的输出。我将在输出中查找cmd.exe。我想知道这是否与区域设置/区域(或Windows版本)不同,
@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