2009-05-25 52 views
30

我正在运行长时间运行的批处理文件。我现在意识到,我必须在批处理文件的末尾添加更多的命令(不更改现有内容,只是一些额外的命令)。是否有可能这样做,因为大多数批处理文件是逐步读取并逐个执行的?或者系统读取文件的全部内容,然后运行作业?在运行时更改批处理文件

+3

你得爱这么快的反应。你已经开始运行批处理>发布了一个问题>有一个答案>在执行完成之前编辑你的文件! – 2014-04-09 04:21:23

+0

另请注意,当批处理文件被删除或重命名时,当前指令完成时会抛出一个错误:“无法找到批处理文件。” – 2014-07-28 23:34:47

回答

28

我刚刚试了一下,对我的直觉,它在最后拿起新的命令(在Windows XP)

我创建了一个包含

echo Hello 
pause 
echo world 

我跑了文件的批处理文件,并在暂停时添加

echo Salute 

保存并按下Enter键以控制暂停,所有三个提示都被回显到控制台。

所以,去吧!

15

命令解释程序会记住它在批处理文件中的行位置。只要你在当前正在执行的行位置后修改批处理文件,你就会好起来的。

如果你修改它之前,它会开始做奇怪的事情(重复命令等..)。

+1

请记录在任何地方,或者这是来自您自己的经验? – Benoit 2012-03-09 13:07:40

+2

这是我的经验。 – UnhandledExcepSean 2012-03-09 13:17:46

3

几乎像rein所说的,cmd.exe记住它当前的文件位置(不仅是行位置),而且对于每次调用,它都会将文件位置推到不可见堆栈上。

这意味着,您可以编辑您的文件,而它的运行和背后的实际文件位置之前,你只需要你做什么...

的自我修改批次的少量样品
它改变了线set value=1000不断

@echo off 
setlocal DisableDelayedExpansion 
:loop 
REM **** the next line will be changed 
set value=1000 
rem *** 
echo ---------------------- 
echo The current value=%value% 
<nul set /p ".=Press a key" 
pause > nul 
echo(
(
call :changeBatch 
rem This should be here and it should be long 
) 
rem ** It is neccessary, that this is also here! 
goto :loop 
rem ... 
:changeBatch 
set /a n=0 
set /a newValue=value+1 
set /a toggle=value %% 2 
set "theNewLine=set value=%newValue%" 
if %toggle%==0 (
    set "theNewLine=%theNewLine% & rem This adds 50 byte to the filesize.........." 
) 
del "%~f0.tmp" 2> nul 
for /F "usebackq delims=" %%a in ("%~f0") DO (
    set /a n+=1 
    set "line=%%a" 
    setlocal EnableDelayedExpansion 
    if !n!==5 (
     (echo !theNewLine!) 
    ) ELSE (
     (echo !line!) 
    ) 
    endlocal 
) >> "%~f0.tmp" 
(
    rem the copy should be done in a parenthesis block 
    copy "%~f0.tmp" "%~f0" > nul 
    if Armageddon==TheEndOfDays (
    echo This can't never be true, or is it? 
) 
) 
echo The first line after the replace action.... 
echo The second line comes always after the first line? 
echo The current filesize is now %~z0 
goto :eof 
7

杰布的例子是很多的乐趣,但它是非常依赖于被添加或删除的文本的长度。我认为反直觉的结果是当他说“如果你修改它之前它会开始做奇怪的事情(重复命令等)”意味着什么。

我修改了jeb的代码,以显示如何在执行批处理文件的开头自由修改不同长度的动态代码,只要适当的填充到位。整个动态部分被每次迭代完全替换。每条动态线都以非干扰;作为前缀。这可以方便地允许FOR /F去掉动态代码,因为隐含的EOL=;选项。

我不寻找特定的行号,而是寻找特定的注释来定位动态代码的开始位置。这更容易维护。

我使用等号行来无害地填充代码以允许扩展和收缩。可以使用以下字符的任意组合:逗号,分号,平等,空格,制表符和/或换行符。 (当然,填充不能以分号开头。)圆括号内的等号允许代码扩展。括号后的等号允许代码收缩。

请注意,FOR /F剥离空行。这个限制可以通过使用FINDSTR在每行前面加上行号来解决,然后在循环中去掉前缀。但额外的代码会减慢速度,所以除非代码依赖空行,否则不值得做。

@echo off 
setlocal DisableDelayedExpansion 
echo The starting filesize is %~z0 
:loop 
echo ---------------------- 
::*** Start of dynamic code *** 
;set value=1 
::*** End of dynamic code *** 
echo The current value=%value% 
:: 
::The 2 lines of equal signs amount to 164 bytes, including end of line chars. 
::Putting the lines both within and after the parentheses allows for expansion 
::or contraction by up to 164 bytes within the dynamic section of code. 
(
    call :changeBatch 
    ============================================================================== 
    ============================================================================== 
) 
================================================================================ 
================================================================================ 
set /p "quit=Enter Q to quit, anything else to continue: " 
if /i "%quit%"=="Q" exit /b 
goto :loop 
:changeBatch 
(
    for /f "usebackq delims=" %%a in ("%~f0") do (
    echo %%a 
    if "%%a"=="::*** Start of dynamic code ***" (
     setlocal enableDelayedExpansion 
     set /a newValue=value+1, extra=!random!%%9 
     echo ;set value=!newValue! 
     for /l %%n in (1 1 !extra!) do echo ;echo extra line %%n 
     endlocal 
    ) 
) 
) >"%~f0.tmp" 
:: 
::The 2 lines of equal signs amount to 164 bytes, including end of line chars. 
::Putting the lines both within and after the parentheses allows for expansion 
::or contraction by up to 164 bytes within the dynamic section of code. 
(
    move /y "%~f0.tmp" "%~f0" > nul 
    ============================================================================== 
    ============================================================================== 
) 
================================================================================ 
================================================================================ 
echo The new filesize is %~z0 
exit /b 

上述工作,但事情如果动态代码是在文件末尾移动到子程序要容易得多。代码可以无限制地扩展和收缩,而不需要填充。在去除动态部分时FINDSTR比FOR/F快得多。动态行可以安全地以分号(包括标签!)作为前缀。然后,FINDSTR/V选项用于排除以分号开头的行,并且可以简单地添加新的动态代码。

@echo off 
setlocal DisableDelayedExpansion 
echo The starting filesize is %~z0 

:loop 
echo ---------------------- 
call :dynamicCode1 
call :dynamicCode2 
echo The current value=%value% 
call :changeBatch 
set /p "quit=Enter Q to quit, anything else to continue: " 
if /i "%quit%"=="Q" exit /b 
goto :loop 

:changeBatch 
(
    findstr /v "^;" "%~f0" 
    setlocal enableDelayedExpansion 
    set /a newValue=value+1, extra=!random!%%9 
    echo ;:dynamicCode1 
    echo ;set value=!newValue! 
    echo ;exit /b 
    echo ; 
    echo ;:dynamicCode2 
    for /l %%n in (1 1 !extra!) do echo ;echo extra line %%n 
    echo ;exit /b 
    endlocal 
) >"%~f0.tmp" 
move /y "%~f0.tmp" "%~f0" > nul 
echo The new filesize is %~z0 
exit /b 

;:dynamicCode1 
;set value=33 
;exit /b 
; 
;:dynamicCode2 
;echo extra line 1 
;exit /b 
2

简答:是的,批处理文件可以在运行时自行修改。正如其他人已经证实。

几年前,在Windows 3之前,我工作的地方在MS-DOS中有一个内部菜单系统。它运行的方式非常优雅:它实际上是从主程序(用C编写)修改以运行脚本的批处理文件运行的。这个窍门意味着菜单程序本身并没有占用内存空间,而选择正在运行。这包括诸如LAN Mail程序和3270终端程序之类的东西。

但是从一个自我修改的批处理文件运行意味着它的脚本也可以执行诸如加载TSR程序之类的事情,事实上可以做任何你可以放在批处理文件中的东西。这使得它非常强大。只有GOTO命令不起作用,直到作者最终想出了如何为每个命令重新启动批处理文件。

2

命令解释程序似乎记住了它正在读取的每个命​​令文件内的字节偏移量,但是文件本身并未锁定,所以可以在文本编辑器运行时进行更改。

如果在记住该位置后对文件进行更改,解释器应该继续执行现在修改的脚本。但是,如果在该点之前进行了更改,并且该修改会更改此时文本的长度(例如,您插入或删除了一些文本),则记住的位置现在不再指该下一个命令的开始。当解释器试图读取下一个'行'时,它将取而代之选择不同的行,或者可能是行的一部分,取决于插入或删除多少文本。如果你幸运的话,它可能无法处理它发生的任何单词登陆,发出错误并继续从下一行执行 - 但仍可能不是你想要的。

但是,通过了解正在发生的事情,您可以构建脚本以降低风险。我的脚本实现了简单的菜单系统,通过显示菜单,使用choice命令接受来自用户的输入,然后处理选择。诀窍是确保脚本等待输入的位置靠近文件的顶部,以便您可能希望进行的任何编辑都将在该位置之后发生,因此不会产生不良影响。

例子:

:top 
call :displayMenu 
:prompt 
REM The script will spend most of its time waiting here. 
choice /C:1234 /N "Enter selection: " 
if ERRORLEVEL == 4 goto DoOption4 
if ERRORLEVEL == 3 goto DoOption3 
if ERRORLEVEL == 2 goto DoOption2 
goto DoOption1 
:displayMenu 
(many lines to display menu) 
goto prompt 
:DoOption1 
(many lines to do Option 1) 
goto top 
:DoOption2 
(many lines to do Option 2) 
goto top 
(etc)