0

The problem is echo !out! show all lines but echo %data% which assigned from !out! only show first line

@echo off

call :f
echo %data%

:f
setlocal EnableDelayedExpansion
set out=
set NL=^


for /f "delims=" %%i in ('dir /b') do (
    if defined out set "out=!out!!NL!"
    set "out=!out!%%i"
)
echo !out!
set data=!out!
exit /b 0

What is the correct way to pass the value entirely to other variable which i need to use freely after endlocal?


Thank you @aschipfl's for your answer that clarify that it's impossible to do it without end up in for-in loop again. Actually all that I want is to simplify my code without have to write for-in loop many times by turn it into a subroutine. I've solved it by passing the subroutine to the subroutine instead.

stackunderflow
  • 1,492
  • 1
  • 23
  • 53
  • this can be completely avoided by disabling delayed expansion, though the escaping is complicated&content of `out` will not be handled properly. see https://stackoverflow.com/a/8257951/12861751 ... – ScriptKidd May 25 '20 at 09:59
  • 2
    I have no real idea why you've created this question, but not specifically told the readers that you're trying to create a variable, to hold the entire content of your `Dir` command's output. My question is What is the reason for using `EndLocal` where you have? _(all it is effectively doing is undefining the variables named `NL` and `out`, and you could have simply used `Set "NL="` and `Set "out="`, or `For %%G In (NL out) Do Set "%%G="` immediately after the `For` loop to do that)_! – Compo May 25 '20 at 11:31
  • @Compo Sorry. I thought it is clearer by simplify the question. I've edited it. – stackunderflow May 26 '20 at 00:09

1 Answers1

2

As derived from this thread, you cannot echo a multi-line string using immediate (%-)expansion, because everything after the first line-break is ignored.

To make your script working you need to correct two issues:

  • before the line endlocal & set data=%out% you must replace every new-line in variable out by an escaped new-line, that is the sequence ^ plus new-line plus new-line, which is exactly the same that you are using for defining the variable NL;
  • echo %data% truncates the displayed string at the first occurrence of a new-line in the value of variable data, so you need to use set data to show the actual content of the variable (or more precisely said, of all variables whose names begin with data);

Both of these items are commented (rem) in the following code:

@echo off
setlocal EnableDelayedExpansion

set out=
set NL=^


for /F "delims= eol=|" %%i in ('dir /b') do (
    if defined out set "out=!out!!NL!"
    set "out=!out!%%i"
)
echo * Original variable content:
set out
rem // Replace every new-line by an escaped new-line:
set out=!out:^%NL%%NL%=^^^%NL%%NL%^%NL%%NL%!
echo * Modified variable content:
set out
endlocal & set data=%out%

rem // Do not use `echo` to show true content of variable:
echo * Returned variable content:
set data
echo * Mistaken variable content:
echo %data%

exit /B 0

Although the variable value is now correctly passed over the endlocal barrier, this approach is not exactly brilliant, because it does not allow you to use variable %data% (again because everything after the first line-break is ignored as initially mentioned), unless you have got delayed expansion enabled in the hosting cmd instance, which would permit to use !data!.


Another remaining problem is that special characters in the multi-line string (like ^, &, (, ) and ", <, >, |) may cause syntax errors or other unexpected issues. However, this can be avoided by using a for meta-variable rather than a normal environment variable for passing the variable value beyond the endlocal barrier, because the former are expanded after special character recognition, in contrast to the latter, which are expanded before:

@echo off
setlocal EnableDelayedExpansion

set out=
set NL=^


for /F "delims=" %%i in ('dir /b') do (
    if defined out set "out=!out!!NL!"
    set "out=!out!%%i"
)
echo # Original variable content:
set out
rem /* Use a `for` meta-variable rather than a normal environment variable to
rem    pass the variable value beyond the `endlocal` barrier;
rem    a standard `for` loop can be used here, because there are not going to be
rem    wildcards `?` and `*` in the variable value since they have already been
rem    resolved by `dir`; `for /F` cannot be used here due to the new-lines: */
for %%j in ("!out!") do endlocal & set "data=%%~j"

rem // Do not use `echo` to show true content of variable:
echo # Returned variable content:
set data
echo # Mistaken variable content:
echo %data%

exit /B 0

The problem not being able to use variable %data% remains though.


To be able to use variable %data% with immediate expansion you could however simply store escaped new-lines rather than literal ones in the variable, because upon expansion you will have the intended literal new-line:

@echo off
setlocal EnableDelayedExpansion

set out=
set NL=^


for /F "delims=" %%i in ('dir /b') do (
    if defined out set "out=!out!!NL!"
    set "out=!out!%%i"
)
echo # Original variable content:
set out
rem // Replace every new-line by an escaped new-line:
set out=!out:^%NL%%NL%=^^^%NL%%NL%^%NL%%NL%!
echo # Modified variable content:
set out
rem /* Use a `for` meta-variable rather than a normal environment variable to
rem    pass the variable value beyond the `endlocal` barrier;
rem    a standard `for` loop can be used here, because there are not going to be
rem    wildcards `?` and `*` in the variable value since they have already been
rem    resolved by `dir`; `for /F` cannot be used here due to the new-lines: */
for %%j in ("!out!") do endlocal & set "data=%%~j"

rem // Do not use `echo` to show true content of variable:
echo # Actual variable content:
set data
echo # Parsed variable content:
echo %data%

exit /B 0

But regard that this is only going to work when %data% does not appear within quoted ("") strings.

aschipfl
  • 33,626
  • 12
  • 54
  • 99