1

In order to store multiple lines inside a variable from a text file, I am using the answer from this question: https://stackoverflow.com/a/10624240/1513458

Using this script inline and parsing large files gave me a maximum setlocal error. So to counter this, I put the script in a subroutine, and called it from the line(s) i needed it in.

An echo at the bottom of the subroutine before it terminates would output the expected result fine... but back at the top where it was originally called, the variable is empty. Why is that?

Here's a shortened version of the script:

setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile %sourcefile%
echo !insert!
goto :EOF

:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ %SOURCELOC%\%1.txt"`) do (
    set "line=%%a"
    SETLOCAL EnableDelayedExpansion
    set "line=!line:#=#S!"
    set "line=!line:*:=!"
    for /F "delims=" %%p in ("!insert!#L!line!") do (
        ENDLOCAL
        set "insert=%%p"
    )
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^

!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
goto :EOF

Thanks so much for all your help. Batch is clearly not meant for me, but i need to tough this one through.

Community
  • 1
  • 1
TurdPile
  • 976
  • 1
  • 7
  • 21
  • Do you really need all the lines in one variable or could it be simpler to use an _array_, one line per variable? – jeb Aug 05 '13 at 20:39

2 Answers2

3

Your :readfile routine has SETLOCAL at the top that localizes environment changes. When a routine ends, there is an implicit ENDLOCAL executed for every active SETLOCAL that was instantiated within the routine. So the environment is restored to the state that existed prior to the call.

There is no need for a called subroutine. Your earlier code must have had SETLOCAL without an ENDLOCAL within the FOR loop. That would cause the max SETLOCAL error. Now your FOR loop has paired SETLOCAL/ENDLOCAL, so no more problem.

It is possible to transport any value accross the ENDLOCAL barrier, but it uses an advanced technique.

Some simple restructuring avoids the need to do that. But be careful - no batch variable can contain more than 8192 characters.

setlocal disableDelayedExpansion
set sourcefile=cc1

set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
  set "line=%%a"
  setlocal EnableDelayedExpansion
  set "line=!line:#=#S!"
  set "line=!line:*:=!"
  for /f "delims=" %%p in ("!insert!#L!line!") do (
    endlocal
    set "insert=%%p"
  )
)
setlocal EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^

!"
set "insert=!insert:#S=#!"
)
echo !insert!


EDIT

Here is a solution that demonstrates returning any value across the ENDLOCAL border (except it does not support carriage return. There is a simple extension that supports carriage return). The routine will give the correct result, regardless whether it was called with delayed expansion enabled or disabled. The technique was developed by DosTips user jeb here:http://www.dostips.com/forum/viewtopic.php?f=3&t=1839, and here: http://www.dostips.com/forum/viewtopic.php?p=6930#p6930. There you will find some explanation as to how it works, but it is not for the faint of heart.

@echo off
setlocal enableDelayedExpansion
set sourceloc=.
set sourcefile=test
set ^"LF=^

^"
call :readFile "%sourcefile%"
echo(!insert!
exit /b

:readFile
setlocal
set "notDelayed=!"

setlocal disableDelayedExpansion
set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
  set "line=%%a"
  setlocal EnableDelayedExpansion
  set "line=!line:%%=%%J!"
  set "line=!line:*:=!"
  for /f "delims=" %%p in ("!insert!%%~L!line!") do (
    endlocal
    set "insert=%%p"
  )
)
setlocal enableDelayedExpansion
if defined insert (
  set "insert=!insert:"=%%~K!"
  if not defined notDelayed set "insert=!insert:^=^^^^!"
)

if defined insert if not defined notDelayed set "insert=%insert:!=^^^!%" Do not remove!
set "replace=%% """"
for %%L in ("!LF!") do for /f "tokens=1,2" %%J in ("!replace!") do (
  endlocal
  endlocal
  endlocal
  set "insert=%insert%" Do not remove!
)
exit /b
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Read the link answer I provided. The script doesn't drop ; because each line is prepended with XXX:, as per /N in findstr. – TurdPile Aug 05 '13 at 17:29
  • Plus the code you provided (inline) is identical to what i had, besides the %~1, this is what caused the main problem to begin with. Setlocal was being called too much. The entire reason of making it a subroutine was for the implicit endlocals. - Unless I am missing what you changed in the code, I don't see the problem being any different. – TurdPile Aug 05 '13 at 17:34
  • @TurdPile - You are correct about EOL not being an issue. I didn't notice that both FOR /F loops are protected, the 1st by the line number, the 2nd by the #L. I removed my note from the answer. – dbenham Aug 05 '13 at 17:54
  • Yes. my problem is strictly as I stated, Either i need to find a way to return the variable from the subroutine, or find out how to call matching endlocals without losing previous variables set above this particular part. – TurdPile Aug 05 '13 at 18:10
  • @TurdPile - My diagnosis of why you are losing the value is spot on - the implicit ENDLOCAL kills the value. I tested my code on a file with more than 100 lines and it worked perfectly, without any setlocal recursion error. I realize this code is an excerpt of a larger script. Is there an outer loop loading multiple files into a variable? If so, then that could account for your setlocal recursion error. Each iteration adds 2 unclosed SETLOCAL. If so, then we need to see more of your code. You might need special code to transport a value containing line feeds across the ENDLOCAL barrier. – dbenham Aug 05 '13 at 18:16
  • @TurdPile - I've added a solution to transport value across ENDLOCAL barrier. – dbenham Aug 05 '13 at 19:04
  • +1 But I suppose that only two of the three endlocals are neccessary. – jeb Aug 05 '13 at 20:36
  • @jeb - ??? The routine has 3 SETLOCAL outside of FOR: 2 at top, and one in middle after FOR. So 3 ENDLOCAL are needed at end. One SETLOCAL/ENDLOCAL pair could be eliminated if the routine is hard wired to be called with delayed expansion enabled. – dbenham Aug 05 '13 at 20:45
  • @dbenham Arrg, I missed the first `setlocal` at the begin of the :readFile – jeb Aug 05 '13 at 20:53
  • @dbenham I will hop in the shower and try the solution you propose and let you know how it turns out. I'm just going to mark as solved regardless, as you've been a great help. Grazie. – TurdPile Aug 05 '13 at 21:08
  • Thank you, sir. It worked, (as I'm sure you knew it would :P) – TurdPile Aug 05 '13 at 22:35
  • @jeb and dbenham: Please take a look at this question if you would. Concerns max variable character limit: http://stackoverflow.com/questions/18091056/file-rewriting-one-line-is-greater-than-variables-max-size-workaround – TurdPile Aug 06 '13 at 21:46
0

try this:

setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile insert
echo %insert%
goto :EOF

:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%1.txt"') do (
     set "line=%%a"
     SETLOCAL EnableDelayedExpansion
     set "line=!line:#=#S!"
     set "line=!line:*:=!"
     for /F "delims=" %%p in ("!insert!#L!line!") do (
          ENDLOCAL
          set "insert=%%p"
     )
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^

!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
SET "%1=!insert!"
ENDLOCAL 
goto :EOF
Endoro
  • 37,015
  • 8
  • 50
  • 63