您好,我是新手,我能够运行一个批处理文件的实例。
但是,正在创建另一个名为something.lock的文件,但是当我停止批处理或关闭它时,该文件本身并未被删除。 新文件create是有助于运行一个实例的文件。 使用“X”关闭脚本后,是否可以删除新文件“.lock”,或者因为用户标记为end而正确结束:
我的代码是
:init
set "started="
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
@if defined started (
del "%~f0.lock"
) else (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
exit /b
:start
cd /d %~dp0
cls
:initial
pause >nul
答案 0 :(得分:2)
您误用了锁定文件。您只是检查文件是否存在,这意味着您必须保证在批量终止时删除该文件。
有一种更好的方法,你只能部分实现。只有一个进程可以打开文件以进行写访问。您只需要确定该文件是否已被其他进程锁定。
一旦具有独占锁的进程终止,锁将被释放。无论脚本如何终止,都是如此 - 即使它是Ctrl-C或窗口关闭的结果。该文件可能不会被删除,但下次运行该脚本时,该文件将不会被锁定,因此脚本将很好地运行。
在下面的代码中,在将sterr重定向到nul之前,我将stderr的当前定义保存到未使用的文件句柄。在内部块中,我将stderr重定向回到已保存的定义。这样,如果文件已被锁定,我会阻止错误消息,但CALLed:start例程仍会正常打印错误消息。
@echo off
:init
8>&2 2>nul ( 2>&8 9>"%~f0.lock" call :start ) || (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
del "%~f0.lock" 2>nul
exit /b
:start
cd /d %~dp0
cls
del asdfasdfasdf
:initial
pause >nul
答案 1 :(得分:0)
难点在于您的批处理线程本身不会拥有自己的PID。没有优雅的方法来判断批处理脚本是在运行还是已经终止。并且没有办法唤醒死者,当用户红色X或 Ctrl + C &#时,让脚本有最后一个字39; S。当它结束时,它结束了。
有几种方法可以做你想做的事。试一试,看看你喜欢哪一个。使用dbenham's solution。他是对的。虽然解决方案4似乎运作良好,但是这里的努力仍然是徒劳的。最后,它仍然只是一个黑客;而dbenham的重定向技巧提供了锁定文件的正确实现,就像锁定文件假设的一样。
...
一种简单的方法是使用powershell
最小化当前窗口,使用start /wait
重新启动脚本,然后在完成后再次调用powershell
进行恢复。
@echo off
setlocal
set "lock=%temp%\~%~n0.lock"
if "%~1" neq "wrapped" (
if exist "%lock%" (
echo Only one instance is allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
rem :: create lock file
>"%lock%" echo 1
rem :: minimize this console
powershell -windowstyle minimized -command ""
rem :: relaunch self with "wrapped" argument and wait for completion
start /wait "" cmd /c "%~f0" wrapped
rem :: delete lock file
del "%lock%"
rem :: restore window
powershell -windowstyle normal -command ""
goto :EOF
)
:: Main script goes here.
:loop
cls
echo Simulating script execution...
ping -n 2 0.0.0.0 >NUL
goto loop
这应该足以供临时使用,并且应该考虑批处理文件终止taskkill /im "cmd.exe" /f
或重启或停电的任何原因。
如果您需要更灵活的解决方案,您可以获取当前控制台窗口的PID并间歇性地测试它是否仍然存在。 start /min
一个辅助窗口,用于监视其父窗口是否死亡,然后删除锁定文件。而且只要您正在创建一个观察者,不妨让观察者 锁定文件。
此方法的最大缺点是它需要使用exit
结束主脚本以销毁控制台窗口,无论您是否希望它被销毁。当剧本找出其父亲的PID时,还会有第二次或第二次暂停。
(使用.bat
扩展名保存,并像运行任何其他批处理脚本一样运行它。)
@if (@a==@b) @end /* JScript multiline comment
:: begin batch portion
@echo off
setlocal
:: locker will be a batch script to act as a .lock file
set "locker=%temp%\~%~nx0"
:: If lock file already exists
if exist "%locker%" (
tasklist /v | find "cleanup helper" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
)
:: get PID of current cmd console window
for /f "delims=" %%I in ('cscript /nologo /e:Jscript "%~f0"') do (
set "PID=%%I"
)
:: Create and run lock bat.
>"%locker%" echo @echo off
>>"%locker%" echo setlocal
>>"%locker%" echo echo Waiting for parent script to finish...
>>"%locker%" echo :begin
>>"%locker%" echo ping -n 2 0.0.0.0^>NUL
>>"%locker%" echo tasklist /fi "PID eq %PID%" ^| find "%PID%" ^>NUL ^&^& ^(
>>"%locker%" echo goto begin
>>"%locker%" echo ^) ^|^| ^(
>>"%locker%" echo del /q "%locker%" ^&^& exit
>>"%locker%" echo ^)
:: Launch cleanup watcher to catch ^C
start /min "%~nx0 cleanup helper" "%locker%"
:: ==================
:: Rest of script
:: blah
:: blah
:: blah
:: ==================
:end
echo Press any key to close this window.
pause >NUL
exit
:: end batch portion / begin JScript
:: https://stackoverflow.com/a/27514649/1683264
:: */
var oShell = WSH.CreateObject('wscript.shell'),
johnConnor = oShell.Exec('%comspec% /k @echo;');
// returns PID of the direct child of explorer.exe
function getTopPID(PID, child) {
var proc = GetObject("winmgmts:Win32_Process=" + PID);
return (proc.name == 'explorer.exe') ? child : getTopPID(proc.ParentProcessID, PID);
}
var PID = getTopPID(johnConnor.ProcessID);
johnConnor.Terminate();
// output PID of console window
WSH.Echo(PID);
您还可以通过在锁定文件中设置时间戳并在控制台窗口标题中设置相同的时间戳来测试锁定文件并查看其是否过时。唯一的问题是,如果用户以 Ctrl + C 终止,则窗口标题不会恢复正常,因此您无法运行脚本两次没有关闭cmd
窗口。但关闭窗口并为随后的发布打开一个新窗口可能不是太可怕的代价,因为这是最简单的方法。
@echo off
setlocal
set "started=%time%"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "locktime="
) else (
set "locktime=%started%"
)
tasklist /v | find "%locktime%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %~nx0 started at %started%
>"%lockfile%" echo %started%
:: rest of script here
echo Simulating script execution...
:loop
ping -n 2 0.0.0.0 >NUL
goto loop
这里有一个更精细的解决方案,它结合了方法1和3.它在同一窗口中重新启动,然后将窗口标题设置为唯一ID。当脚本正常退出时,将删除锁定文件。无论脚本是正常还是强制退出,窗口标题都会恢复为默认值。如果任务列表中没有与标题匹配唯一ID的窗口,则锁定文件将被视为过时并被覆盖。否则,脚本会通知用户只允许一个实例退出。这是我最喜欢的解决方案。
@echo off
setlocal
if "%~1" neq "wrapped" (
cmd /c "%~f0" wrapped %*
goto :EOF
)
:: remove "wrapped" first argument
shift /1
:: generate unique ID string
>"%temp%\~%~n0.a" echo %date% %time%
>NUL certutil -encode "%temp%\~%~n0.a" "%temp%\~%~n0.b"
for /f "usebackq EOL=- delims==" %%I in ("%temp%\~%~n0.b") do set "running_id=%%I"
del "%temp%\~%~n0.a" "%temp%\~%~n0.b"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "lock_id="
) else (
set "lock_id=%running_id%"
)
tasklist /v | find "%lock_id%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %running_id%
>"%lockfile%" echo %running_id%
:: rest of script here
echo Press any key to exit gracefully, or Ctrl+C to break
pause >NUL
del "%lockfile%"
goto :EOF