简体   繁体   English

在运行时更改批处理文件

[英]Changing a batch file when its running

I am running a long running batch file.我正在运行一个长时间运行的批处理文件。 I now realize that I have to add some more commands at the end of the batch file (no changes to exisiting content, just some extra commands).我现在意识到我必须在批处理文件的末尾添加更多命令(对现有内容没有更改,只是一些额外的命令)。 Is it possible to do this, given that most batch files are read incrementally and executed one by one?鉴于大多数批处理文件都是增量读取并一个一个执行的,是否可以这样做? Or does the system read the entire contents of the file and then runs the job?还是系统读取文件的全部内容然后运行作业?

I just tried it, and against my intuition, it picked up the new commands at the end (on Windows XP)我刚刚尝试过,并且违背我的直觉,它最后选择了新命令(在 Windows XP 上)

I created a batch file containing我创建了一个批处理文件,其中包含

echo Hello
pause
echo world

I ran the file, and while it was paused, added我运行了该文件,并在暂停时添加了

echo Salute

Saved it and pressed enter to contine the pause, all three prompts were echoed to the console.保存并按回车键继续暂停,所有三个提示都回响到控制台。

So, go for it!所以,加油吧!

The command interpreter remembers the line position byte offset it's at in the batch file.命令解释器记住它在批处理文件中的行位置字节偏移量 You will be fine as long as you modify the batch file after the current executing line position byte offset at the end of the most recently parsed line of code .只要在最近解析的代码行末尾当前执行行位置字节偏移之后修改批处理文件,就可以了。

If you modify it before then it will start doing strange things (repeating commands etc..).如果您在此之前修改它,它将开始做奇怪的事情(重复命令等)。

jeb's example is a lot of fun, but it is very dependent on the length of the text that is added or deleted. jeb 的例子很有趣,但是它非常依赖于添加或删除的文本的长度。 I think the counter-intuitive results are what rein meant when he said "If you modify it before then it will start doing strange things (repeating commands etc..)".我认为反直觉的结果就是当他说“如果你在此之前修改它之前它会开始做奇怪的事情(重复命令等)”时的意思。

I've modified jeb's code to show how dynamic code of varying length can be freely modified at the beginning of an executing batch file as long as appropriate padding is in place.我已经修改了 jeb 的代码,以展示如何在执行批处理文件的开头自由修改不同长度的动态代码,只要适当的填充就位。 The entire dynamic section is completely replaced with each iteration.每次迭代都完全替换整个动态部分。 Each dynamic line is prefixed with a non interfering ;每个动态行都以非干扰前缀; . . This conveniently allows FOR /F to strip the dynamic code because of the implicit EOL=;由于隐含的EOL=;这方便地允许FOR /F剥离动态代码EOL=; option.选项。

Instead of looking for a particular line number, I look for a specific comment to locate where the dynamic code begins.我不是寻找特定的行号,而是寻找特定的注释来定位动态代码的开始位置。 This is easier to maintain.这更容易维护。

I use lines of equal signs to harmlessly pad the code to allow for expansion and contraction.我使用等号行来无害地填充代码以允许扩展和收缩。 Any combination of the following characters could be used: comma, semicolon, equal, space, tab and/or newline.可以使用以下字符的任意组合:逗号、分号、等号、空格、制表符和/或换行符。 (Of course the padding cannot begin with a semicolon.) The equal signs within the parentheses allow for code expansion. (当然填充不能以分号开头。)括号内的等号允许代码扩展。 The equal signs after the parentheses allow for code contraction.括号后的等号允许代码收缩。

Note that FOR /F strips empty lines.请注意, FOR /F去除空行。 This limitation could be overcome by using FINDSTR to prefix each line with the line number and then strip out the prefix within the loop.可以通过使用 FINDSTR 为每行添加行号前缀,然后在循环中去掉前缀来克服此限制。 But the extra code slows things down, so it's not worth doing unless the code is dependent on blank lines.但是额外的代码会减慢速度,因此除非代码依赖于空行,否则不值得这样做。

@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

The above works, but things are much easier if the dynamic code is moved to a subroutine at the end of the file.以上工作,但如果动态代码移动到文件末尾的子程序,事情会容易得多。 The code can expand and contract without limitation, and without the need for padding.代码可以不受限制地扩展和收缩,并且不需要填充。 FINDSTR is much faster than FOR /F at removing the dynamic portion. FINDSTR 在删除动态部分时比 FOR /F 快得多。 Dynamic lines can be safely be prefixed with a semicolon (including labels!).动态行可以安全地以分号作为前缀(包括标签!)。 Then the FINDSTR /V option is used to exclude lines that begin with a semicolon and the new dynamic code can simply be appended.然后 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

Short answer: yes, batch files can modify themselves whilst running.简短回答:是的,批处理文件可以在运行时自行修改。 As others have already confirmed.正如其他人已经证实的那样。

Years and years ago, back before Windows 3, the place I worked had an inhouse menu system in MS-DOS.多年前,在 Windows 3 之前,我工作的地方有一个使用 MS-DOS 的内部菜单系统。 The way it ran things was quite elegant: it actually ran from a batch file that the main program (written in C) modified in order to run scripts.它运行的方式非常优雅:它实际上是从一个批处理文件运行的,主程序(用 C 编写)修改了该文件以运行脚本。 This trick meant that the menu program itself was not taking up memory space whilst selections were running.这个技巧意味着菜单程序本身在选择运行时不会占用内存空间。 And this included things like the LAN Mail program and the 3270 terminal program.这包括 LAN Mail 程序和 3270 终端程序之类的东西。

But running from a self-modifying batch file meant its scripts could also do things like load TSR programs and in fact could do pretty much anything you could put in a batch file.但是从一个自修改的批处理文件运行意味着它的脚本也可以做诸如加载 TSR 程序之类的事情,实际上可以做任何你可以放在批处理文件中的事情。 Which made it very powerful.这使它非常强大。 Only the GOTO command didn't work, until the author eventually figured out how to make the batch file restart itself for each command.只有GOTO命令不起作用,直到作者最终想出了如何让批处理文件为每个命令重新启动。

Nearly like rein said, cmd.exe remember the file position (not only the line position) it's currently is, and also for each call it push the file position on an invisble stack.几乎就像rein所说的那样,cmd.exe 记住它当前所在的文件位置(不仅是行位置),而且对于每次调用,它都会将文件位置推送到一个不可见的堆栈上。

That means, you can edit your file while it's running behind and before the actual file position, you only need to know what you do ...这意味着,您可以在文件在实际文件位置之前和之后运行时对其进行编辑,您只需要知道您在做什么...

A small sample of an self modifying batch自修改批次的小样本
It changes the line set value=1000 continuously连续改变线路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 

The command interpreter appears to remember the byte offset within each command file it is reading, but the file itself is not locked, so it is possible to make changes, say with a text editor, whilst it is running.命令解释器似乎会记住它正在读取的每个命​​令文件中的字节偏移量,但文件本身并未锁定,因此可以在运行时进行更改,例如使用文本编辑器。

If a change is made to the file after this remembered location, the interpreter should happily continue to execute the now modified script.如果在这个记住的位置之后对文件进行了更改,解释器应该很乐意继续执行现在修改过的脚本。 However if the change is made before that point, and that modification changes the length of the text at that point (for example you've inserted or removed some text), that remembered location is now no longer referring to the start of that next command.但是,如果在该点之前进行了更改,并且该修改更改了该点的文本长度(例如,您插入或删除了一些文本),则该记住的位置现在不再指下一个命令的开始. When the interpreter tries to read the next 'line' it will instead pick up a different line, or possibly part of a line depending on how much text was inserted or removed.当解释器尝试读取下一个“行”时,它会选择不同的行,或者可能是一行的一部分,具体取决于插入或删除了多少文本。 If you're lucky, it will probably not be able to process whatever word it happen to land on, give an error and continue to execute from the next line - but still probably not what you want.如果你很幸运,它可能无法处理它遇到的任何单词,给出错误并从下一行继续执行 - 但仍然可能不是你想要的。

However, with understanding of what's going on, you can structure your scripts to reduce the risk.但是,通过了解正在发生的事情,您可以构建脚本以降低风险。 I have scripts that implement a simply menu system, by displaying a menu, accepting input from the user using the choice command and then processing the selection.我有实现简单菜单系统的脚本,通过显示菜单,使用choice命令接受来自用户的输入,然后处理选择。 The trick is to ensure that the point where the script waits for input is near the top of the file, so that any edits you might wish to make will occur after that point and so have no nasty impacts.诀窍是确保脚本等待输入的点靠近文件的顶部,这样您可能希望进行的任何编辑都将在该点之后进行,因此不会产生不利影响。

Example:例子:

: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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM