【问题标题】:After terminate the batch needs to delete a .lock file that has been create终止批处理后需要删除已创建的 .lock 文件
【发布时间】:2015-01-02 19:13:04
【问题描述】:

您好,我是新手,我能够运行批处理文件的一个实例。

但是,正在创建另一个名为 something.lock 的文件,但是当我停止批处理或关闭它时,该文件并没有被自己删除。 新文件创建有助于让一个实例运行。 在我用“X”关闭脚本后或者因为用户正确地结束了标签结束后,新文件“.lock”是否可以被删除:

我的代码是

: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

【问题讨论】:

  • 如果用户单击“X”或以其他方式强制退出(例如 Ctrl+C),执行将停止,您将无法进行清理。

标签: batch-file


【解决方案1】:

您误用了锁定文件。您只是检查文件是否存在,这意味着您必须保证在批处理终止时删除该文件。

有一个更好的方法,您只实现了部分。只有一个进程可以打开文件以进行写访问。您只需要确定文件是否已被另一个进程锁定。

一旦拥有独占锁的进程终止,锁就会被释放。无论脚本如何终止都是如此 - 即使它是 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

【讨论】:

【解决方案2】:

困难在于您的批处理线程本身不会有自己的 PID。没有优雅的方法来判断您的批处理脚本是否正在运行或何时终止。并且没有办法唤醒死者,让脚本在用户红色 X 或 Ctrl+C 时拥有最终决定权。当它结束时,它就结束了。

有几种方法可以做你想做的事。全部尝试一下,看看你喜欢哪个。使用dbenham's solution。他是正确的。尽管解决方案 4 似乎工作得很好,但下面的努力留在这里作为徒劳的练习。最后,它仍然只是一个 hack;而 dbenham 的重定向技巧提供了锁定文件的正确实现方式,应该锁定文件的工作方式。

...

解决方案 1

一种简单的方法是使用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 之外终止或重新启动或断电的任何原因。


解决方案 2

如果您需要更安全的解决方案,您可以获取当前控制台窗口的 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);

解决方案 3

您还可以通过在锁定文件中设置时间戳并在控制台窗口标题中设置相同的时间戳来测试锁定文件并查看它是否过时。唯一的问题是,如果用户使用 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

解决方案 4

这里有一个更精致的解决方案,结合了方法 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多