Changing a batch file when its running

后端 未结 6 688
北荒
北荒 2020-12-01 05:22

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 co

相关标签:
6条回答
  • 2020-12-01 05:54

    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. 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)
    
    0 讨论(0)
  • 2020-12-01 06:02

    jeb's example is a lot of fun, but it is very dependent on the length of the text that is added or deleted. 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. 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=; 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. 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. 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. 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.

    @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
    
    0 讨论(0)
  • 2020-12-01 06:03

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

    0 讨论(0)
  • 2020-12-01 06:08

    I just tried it, and against my intuition, it picked up the new commands at the end (on 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!

    0 讨论(0)
  • 2020-12-01 06:09

    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. 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. 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.

    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. 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.

    0 讨论(0)
  • 2020-12-01 06:09

    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.

    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

    @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 
    
    0 讨论(0)
提交回复
热议问题