9

In Linux (Bash) there is a very useful functionality for dumping literal text out to another file like this:

cat > see.txt << EOF
contents going into
my file
EOF

What I need is the equivalent for a Windows batch script. I haven't found this kind of functionality built-in, but I was thinking I could write a subroutine to do it (I don't want to rely on anything that isn't natively in Windows since XP), but I'm having trouble. Here's what I have so far with help from various sources:

call:catMyChunk myCustomText c:\see.txt
exit /b

goto:myCustomText
   This is my test file
   Hope you like it.

    <got these>
    % and these %
    ! these too

   yeah
:myCustomText

:catMyChunk
    ::Should call this function with 2 args, MYDELIM and outFile.txt
    ::where is to be catted to outFile.txt
    ::and text starts with <beginning of line>goto:MYDELIM
    ::and ends with <beginning of line>:MYDELIM
    set searchStart=goto:%~1
    set searchStop=:%~1
    set outFile=%~2
    set startLine=0
    set endLine=0
    for /f "delims=:" %%a in ('findstr -b -n !searchStart! %~dpnx0') do set "startLine=%%a"
    for /f "delims=:" %%a in ('findstr -b -n !searchStop!  %~dpnx0') do set "endLine=%%a"

    set /a linesLeftToRead=%endLine% - %startLine%
    del %outFile%
    if "%linesLeftToRead%" LEQ "0" (
        echo Error finding start and end delmieters for %searchStop% in catMyChunk routine
        exit /B 1
    )
    setlocal DisableDelayedExpansion
    for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
        set "oneLine=%%a"
        setlocal EnableDelayedExpansion
        set "oneLine=!oneLine:*:=!"
        set /a linesLeftToRead-=1
        if !linesLeftToRead! LEQ 0 exit /B
        echo(!oneLine!>>%outFile%
    )

goto: EOF

So my requirement is that the chunk of text is output literally without any changes whatsoever (i.e. I don't want to have to escape blank lines, %, !, <, etc.). This code is doing almost everything I need, except I haven't found a way to get the exclamation points output properly. Here is the output I get, which isn't quite right:

   This is my test file
   Hope you like it.

    <got these>
    % and these %
     these too

   yeah

Edit: For anyone wanting the modified version of the subroutine that now works, here it is:

:catMyChunk
    ::Should call this function with 2 args, MYDELIM and outFile.txt
    ::where is to be catted to outFile.txt
    ::and text starts with <beginning of line>goto:MYDELIM
    ::and ends with <beginning of line>:MYDELIM
    set searchStart=goto:%~1
    set searchStop=:%~1
    set outFile=%~2
    set startLine=0
    set endLine=0
    for /f "delims=:" %%a in ('findstr -b -n !searchStart! %~dpnx0') do set "startLine=%%a"
    for /f "delims=:" %%a in ('findstr -b -n !searchStop!  %~dpnx0') do set "endLine=%%a"

    set /a linesLeftToRead=%endLine% - %startLine%
    del %outFile%
    if "%linesLeftToRead%" LEQ "0" (
        echo Error finding start and end delmieters for %searchStop% in catMyChunk routine
        exit /B 1
    )
    setlocal DisableDelayedExpansion
    for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
        set "oneLine=%%a"
        set /a linesLeftToRead-=1
        setlocal EnableDelayedExpansion
        set "oneLine=!oneLine:*:=!"
        if !linesLeftToRead! LEQ 0 exit /B
        echo(!oneLine!>>%outFile%
        endlocal
    )
    endlocal

goto: EOF
Community
  • 1
  • 1
Jared
  • 1,887
  • 3
  • 20
  • 45

2 Answers2

4

Your code is missing a single endlocal in your FOR-loop.

You will then create for each loop a new local-context through the setlocal EnableDelayedExpansion, this will explode with some more lines in your text file.

Also, to preserve the exclamation marks (and also the carets) you need the toggling technique: DOS batch files: How to read a file?

setlocal DisableDelayedExpansion
for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
    set "oneLine=%%a"
    setlocal EnableDelayedExpansion
    set "oneLine=!oneLine:*:=!"
    set /a linesLeftToRead-=1
    if !linesLeftToRead! LEQ 0 exit /B
    echo(!oneLine!>>%outFile%
    endlocal
)
Efren
  • 4,003
  • 4
  • 33
  • 75
jeb
  • 78,592
  • 17
  • 171
  • 225
  • The "endlocal" was the critical part I was missing. Actually I was missing two of them, since I only wanted "DisableDelayedExpansion" set for that loop. Also, I had to pull the "set /a linesLeftToRead=-1" to outside the loop-localized setlocal. I've added the fixed version of the subroutine to my question for anyone that needs it. Thanks for the help. – Jared Oct 31 '11 at 15:21
3

+1, Interesting application! I modified your code for a simpler and faster version:

@echo off
call :catMyChunk myCustomText see.txt
exit /b

goto:myCustomText
This is my test file
Hope you like it.
<got these>
% and these %
! these too
yeah
:myCustomText

:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
setlocal DisableDelayedExpansion
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
if exist %outFile% del %outFile%
set copyFlag=No
echo No> copyFlag
for /f "delims=" %%a in (%~f0) do (
    set "oneLine=%%a"
    setlocal EnableDelayedExpansion
    if !copyFlag! == No (
        if !oneLine! == %searchStart% echo Yes> copyFlag
    ) else (
        if !oneLine! == %searchStop% (
            echo No> copyFlag
        ) else (
            echo/!oneLine!>> %outFile%
        )
    )
    endlocal
    set /p copyFlag=< copyFlag
)
endlocal
goto :eof

I also created another version that looks more like the Linux version, that is, the lines to copy are placed directly after invoking the routine, and the execution continue after the last copied line. Of course, to make this possible the routine is not invoked via call, but entered with a goto, and when the routine ends it execute a goto %MYDELIM% instead of a "return" (exit /b or goto :eof). Also, because a goto can not have parameters, the "parameters" are defined in variables before the invocation.

@echo off
set searchStop=EndOfMyText
set outFile=see.txt
goto :catMyChunk EndOfMyText
This is my test file
Hope you like it.
<got these>
% and these %
! these too
yeah
:EndOfMyText
exit /b


:catMyChunk
::Before JUMP to this "function" define 2 vars: searchStop and outFile
::where is to be catted to %outFile%
::and text starts with goto :catMyChunk %searchStop%
::and ends with :%searchStop%
setlocal DisableDelayedExpansion
set searchStart=goto :catMyChunk %searchStop%
if exist %outFile% del %outFile%
set copyFlag=No
echo No> copyFlag
for /f "delims=" %%a in (%~f0) do (
    set "oneLine=%%a"
    setlocal EnableDelayedExpansion
    if !copyFlag! == No (
        if /I !oneLine! == !searchStart! echo Yes> copyFlag
    ) else (
        if !oneLine! == :%searchStop% (
            echo No> copyFlag
        ) else (
            echo/!oneLine!>> %outFile%
        )
    )
    endlocal
    set /p copyFlag=< copyFlag
)
endlocal
goto %searchStop%

EDIT

This new version is even faster and now works with all special cases, including empty lines:

:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
setlocal EnableDelayedExpansion
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
if exist %outFile% del %outFile%
findstr /n ^^ "%~f0" > pipeline.txt
call :seekMyChunk < pipeline.txt
del pipeline.txt
exit /B

:seekMyChunk
set oneLine=:EOF
set /P oneLine=
if !oneLine! == :EOF goto startNotFound
set oneLine=!oneLine:*:=!
if not !oneLine! == %searchStart% goto seekMyChunk
:catNextLine
set oneLine=:EOF
set /P oneLine=
if !oneLine! == :EOF goto stopNotFound
set oneLine=!oneLine:*:=!
if !oneLine! == %searchStop% goto :eof
echo/!oneLine!>> %outFile%
goto catNextLine
:startNotFound
echo Error finding start delimiter for %searchStart% in catMyChunk 
goto :eof
:stopNotFound
echo Error finding stop delimiter for %searchStop% in catMyChunk 
goto :eof
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • +1 for extend the code. Be sure to also check all the special cases including empty lines, ^, and &. I tested your second version and it seems to not copy empty lines. That was important to me (may not be for you). That's why I used the `findstr /n`. – Jared Nov 01 '11 at 13:18
  • @Jared: Check my new version, that problem was fixed. – Aacini Nov 02 '11 at 00:39
  • The second version removes trailing white spaces from lines – jeb Nov 02 '11 at 04:47