【问题标题】:Changing a batch file when its running在运行时更改批处理文件
【发布时间】:2010-10-28 17:46:36
【问题描述】:

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

【问题讨论】:

  • 您一定会喜欢如此快速的响应。您已经开始运行批处理 > 发布了一个问题 > 得到了答案 > 在执行完成之前编辑了您的文件!
  • 还要注意,当批处理文件被删除或重命名时,在当前指令完成的那一刻会抛出错误:“找不到批处理文件。”

标签: windows batch-file


【解决方案1】:

命令解释器记住它在批处理文件中的行位置 字节偏移。只要在当前执行行位置 最近解析的代码行末尾的字节偏移量之后修改批处理文件就可以了。

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

【讨论】:

  • 请在任何地方记录,还是根据您自己的经验?
  • 根据我的经验这是正确的。
  • 实际上,它会做的是解析器指针将保持在文件中的相同索引处,因此在索引之前添加/删除文本将移动指令指针下的内容。确实发生了奇怪的事情。
  • 举个例子——现实生活中的funky bug就是这样做的原因。
  • 通常我不会像我一样编辑别人的答案,但人们将行位置解释为行号,这是错误的。许多赞成票导致人们得到不正确的信息。
【解决方案2】:

jeb's example 很有趣,但它很大程度上取决于添加或删除的文本的长度。我认为反直觉的结果就是 rein 所说的“如果你之前修改它,那么它将开始做奇怪的事情(重复命令等)”。

我已经修改了 jeb 的代码,以展示如何在执行批处理文件的开头随意修改可变长度的动态代码,只要有适当的填充。每次迭代都会完全替换整个动态部分。每条动态行都以不干扰的; 为前缀。由于隐含的EOL=; 选项,这方便FOR /F 剥离动态代码。

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

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

请注意,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

【讨论】:

  • EDIT - 我修改了最后一个代码来证明即使是标签也可以加上前缀,因此它们可以很容易地包含在动态代码中。
  • 我可以在执行位置之前注释一些行吗? (如果只计算行数,加上一些额外的字符应该没问题吧?)
  • @JinSnow - 不,Rein's answer 不准确。批处理记住字节偏移量(从文件开头的偏移量),而不是行号。我已经编辑了 Rein 的答案。
【解决方案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 

【讨论】:

  • 这是否意味着在执行行之前(运行时)注释某些行是安全的?
  • @JinSnow 不会,因为用REM添加cmets会添加字符,并且会改变当前文件位置下的代码。它仅在您不更改文件大小时才有效。例如,允许将set myVar=Test修改为REM myVar=Test
【解决方案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)

【讨论】:

    【解决方案5】:

    简短回答:是的,批处理文件可以在运行时自行修改。正如其他人已经确认的那样。

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

    但是从自修改批处理文件运行意味着它的脚本也可以执行诸如加载 TSR 程序之类的操作,实际上可以执行您可以放入批处理文件中的任何事情。这使它非常强大。只有GOTO 命令不起作用,直到作者最终弄清楚如何让批处理文件为每个命令自行重启。

    【讨论】:

      【解决方案6】:

      我刚试了一下,结果出乎我的意料,它最后选择了新命令(在 Windows XP 上)

      我创建了一个包含

      的批处理文件
      echo Hello
      pause
      echo world
      

      我运行了文件,并在它暂停时添加了

      echo Salute
      

      保存并按回车继续暂停,所有三个提示都回显到控制台。

      所以,加油!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-08
        • 1970-01-01
        • 2011-07-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多