更改正在运行的批处理文件

49

我正在运行一个长时间运行的批处理文件。现在我意识到需要在批处理文件末尾添加一些命令(不对现有内容进行修改,只是增加一些额外的命令)。鉴于大多数批处理文件是逐步读取并逐个执行的,这是否可以实现?或者系统是否会读取整个文件内容然后运行作业?


5
你 gotta 爱 SO 的快速响应。你已经开始运行批处理 > 发布一个问题 > 得到一个答案 > 在执行完成之前编辑了你的文件! - Avi Turner
还要注意,当批处理文件被删除或重命名时,在当前指令完成后会抛出一个错误:"找不到批处理文件。" - Tim Kuipers
6个回答

38

我刚试过了,在 Windows XP 上,尽管违反我的直觉,它也能够在末尾捕获到新的命令。

我创建了一个包含以下内容的批处理文件:

echo Hello
pause
echo world
我运行了文件,当它在暂停时,添加了
echo Salute

保存后按回车键继续暂停,所有三个提示都被输出到控制台。

所以,就这样做吧!


37

命令解释器会记住批处理文件中字节偏移量的位置。只要在当前代码行执行结束时,修改批处理文件,就不会有问题。

如果你在此之前进行修改,它将开始出现奇怪的行为(如重复命令等)。


2
请问这个有文档记录吗,还是你自己的经验? - Benoit
5
根据我的经验,这是正确的。 - UnhandledExcepSean
1
实际上,它所做的是解析器指针将保持在文件中相同的索引处,因此在指令指针下方添加/删除文本将会导致发生奇怪的事情。 - cat
1
例子 - 一个现实中的 问题 是由于这样做引起的。 - OmerB
1
通常情况下,我不会像我所做的那样编辑别人的答案,但是人们将行位置解释为行号,这是错误的。许多赞成票导致人们得出不正确的信息。 - dbenham
这适用于 *.cmd 文件吗? - François Beaune

10

jeb's example很有趣,但它非常依赖于添加或删除的文本长度。我认为当rein说“如果在此之前修改它,它将开始做奇怪的事情(重复命令等)”时,他指的就是这种反直觉的结果。

我修改了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 :changeBatch
call :dynamicCode1
call :dynamicCode2
echo The current value=%value%
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

1
编辑 - 我修改了最后的代码,以证明甚至标签也可以添加前缀,因此它们可以轻松地包含在动态代码中。 - dbenham
我可以在执行位置之前注释一些行吗?(如果只计算行数,那么在其上添加一些额外字符应该是可以的,对吧?) - JinSnow
@JinSnow - 不,Rein的回答是不准确的。批处理会记住字节偏移量(从文件开头的偏移量),而不是行号。我已经编辑了Rein的回答。 - dbenham

9
简短回答:是的,批处理文件可以在运行时修改自己。正如其他人已经确认了的那样。
很多年以前,在Windows 3之前,我所在的公司有一个MS-DOS内部菜单系统。它运行的方式非常优雅:它实际上是从一个批处理文件中运行的,这个批处理文件由主程序(用C编写)修改,以便运行脚本。这个技巧意味着当选择运行时,菜单程序本身不占用内存空间。而且这还包括诸如LAN邮件程序和3270终端程序之类的东西。
但是从自修改的批处理文件中运行意味着它的脚本也可以做一些事情,例如加载TSR程序,实际上几乎可以做任何你可以放在批处理文件中的事情。这使得它非常强大。只有GOTO命令无法工作,直到作者最终想出了如何使批处理文件为每个命令重新启动的方法。

7
几乎像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 

这是否意味着在执行代码时注释掉某些行是安全的? - JinSnow
@JinSnow 不行,因为使用“REM”添加注释会增加字符,并且当前文件位置下的代码将会被修改。只有在您不改变文件大小时才有效。例如,可以将“set myVar = Test”修改为“REM myVar = Test”。 - jeb

4
命令解释器似乎记住它正在读取的每个命令文件中的字节偏移量,但文件本身没有被锁定,因此在运行时可以进行更改,比如使用文本编辑器。如果在这个记住的位置之后对文件进行更改,则解释器应该能够愉快地继续执行现在已修改的脚本。 但是,如果在那个点之前进行了更改,并且该修改更改了该点处文本的长度(例如,您插入或删除了一些文本),则该记住的位置现在不再指向下一个命令的开头。 当解释器尝试读取下一个“行”时,它将会选择另一行,或者可能是部分行,具体取决于插入或删除了多少文本。 如果你很幸运,它可能无法处理它所选定的任何单词,给出错误并继续从下一行执行,但仍然可能不是你想要的结果。然而,通过理解发生的情况,您可以构建脚本以减少风险。我有一些脚本,实现了一个简单的菜单系统,通过显示菜单,接受用户输入使用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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接