FOR /F with Line Feed

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Aacini
Expert
Posts: 1596
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: FOR /F with Line Feed

#16 Post by Aacini » 04 Aug 2018 18:44

The method I posted before does not manage "constant strings" in a FOR command, but replaces a character in a variable by a 'LF'. Here it is:

Code: Select all

@echo off
setlocal EnableDelayedExpansion

set "list=Item1 Item2 Item? Item*"

for /F "delims=" %%a in (^"!list: ^=^
% Do NOT remove this line %
!^") do (
  echo %%a
)
However, it is possible to manage "constant strings" in a FOR command, although the syntax is not pretty:

Code: Select all

for /F "tokens=1,2 delims=," %%G in (^"^
1^,Martini Shaker^

2^,Wine Bottle^

3^,Beer Glass^

^") do echo T1=%%G T2=%%H
Output:

Code: Select all

T1=1 T2=Martini Shaker
T1=2 T2=Wine Bottle
T1=3 T2=Beer Glass
The main problem is that if the delimiter is a FOR delimiter character (comma, semicolon or equal-sign), then it must be escaped... Note that the empty lines between values are mandatory.

It is simpler to do the same via a variable, with a better syntax:

Code: Select all

set ^"str=^
1,Martini Shaker^

2,Wine Bottle^

3,Beer Glass^"

for /F "tokens=1,2 delims=," %%G in ("!str!") do echo T1=%%G T2=%%H
You may also replace the embedded 'LF's in a variable:

Code: Select all

echo Original string with embedded LF's:
echo [!str!]
echo/

echo Replace each 'LF' by ']+LF+['
echo [!str:^
% This is the LF to search for %
=]^
% This is the LF to replace %
[!]
Output:

Code: Select all

Original string with embedded LF's:
[1,Martini Shaker
2,Wine Bottle
3,Beer Glass]

Replace each 'LF' by ']+LF+['
[1,Martini Shaker]
[2,Wine Bottle]
[3,Beer Glass]
Antonio

aGerman
Expert
Posts: 3605
Joined: 22 Jan 2010 18:01
Location: Germany

Re: FOR /F with Line Feed

#17 Post by aGerman » 05 Aug 2018 04:56

So in Squashman's case who tried to work with the line feed variable it means that he has to escape the commas if he wants the string literals directly in the arguments clause. Alternatively the string can be saved in a variable where the comma escaping isn't necessary provided delayed expansion is used in the argument clause of the FOR loop. Is that correct Antonio?
Then the latter could look like that:

Code: Select all

echo off &setlocal DisableDelayedExpansion
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)

set ^"str=^
1,Martini!!! Shaker%\n%
2,Wine Bottle%\n%
3,Beer Glass%\n%
^"

setlocal EnableDelayedExpansion
for /F "tokens=1,2 delims=," %%G in ("!str!") do (
  if "!!"=="" endlocal
  echo T1=%%G T2=%%H
)

pause

@Dave I understood the double expansion. Escaping the line feed would be sufficient in that case as you pointed out. As to jeb's original \n variable I still try to understand what it means in terms of the resulting characters. Does the additional SET command lead to triple expansion and the result after the macro expansion is still a single line feed? I'm aware of the addidional escaping, that's not what I'm talking about.

Steffen

dbenham
Expert
Posts: 2218
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: FOR /F with Line Feed

#18 Post by dbenham » 05 Aug 2018 18:39

aGerman wrote:
05 Aug 2018 04:56
@Dave I understood the double expansion. Escaping the line feed would be sufficient in that case as you pointed out. As to jeb's original \n variable I still try to understand what it means in terms of the resulting characters. Does the additional SET command lead to triple expansion and the result after the macro expansion is still a single line feed? I'm aware of the addidional escaping, that's not what I'm talking about.
No, I'm pretty sure the macro code only gets parsed twice - Once for the definition, and once for the expansion/execution.
dbenham wrote:
04 Aug 2018 09:39
I'm thinking this simpler syntax could be used for macro definition as well, though I would probably use \n for the variable name instead of ;
dbenham wrote:
04 Aug 2018 12:22
I'm finally at a PC, and no, it does not work for macros.

This

Code: Select all

(set \n=^
%= This defines an escaped line feed - DO NOT ALTER =%
)

echo Hello%\n%
world!
Is just a more pleasing way of writing

Code: Select all

echo Hello^

world
Both the above only get parsed once. The same is true with the IN() clause in earlier posts. But the macro must be parsed twice, once to define the macro, and again to execute the macro. So the more complicated definition is required to define a macro.
I didn't test properly - The simplified \n definition does work for macros after all :!: :D

My first error in testing was the result of forgetting that the line feeds within a macro definition only work if they are within parentheses, even though I describe the behavior of <LF> parsing fairly well in Phase 2 (partly at the top, and partly later on regarding parentheses processing).

The other mistake is not realizing that both \n = ^<LF><LF>^ and \n = ^<LF> produce the exact same macro definition :!:
They both result in a single <LF> being inserted into the definition, though the path to that result varies a bit.

The original complex definition has ^<LF><LF>^<CR><LF> at the end of the line. The <CR> is stripped in Phase 1.5. Then in Phase 2 the ^<LF><LF> is converted to an escaped <LF> that is not stripped. Then the ^<LF> is stripped, and the next line is read and appended, with the first character of of that line escaped.

The simpler definition has ^<LF><CR><LF> at the end of the line. The <CR> is stripped in Phase 1.5, and then in Phase 2 the ^<LF><LF> is converted to an escaped <LF> that is not stripped, and the next line is simply appended.

Since batch macros with arguments always have an outer FOR statement, all the <LF> are within parentheses, so everything works.

Here is a test script that demonstrates that both \n forms give the same result, and parentheses are needed for the macro to work. The script uses hexdump.bat to examine the byte values within the variable values.

Code: Select all

@echo off
setlocal enableDelayedExpansion

(set LF=^
%= This defines a Line Feed - DO NO ALTER =%
)

(set \n=^^^%LF%%LF%^%LF%%LF%^^)
call :test ^^^^LFLF^^^^

(set \n=^^^
%= This defines an escaped Line Feed - DO NOT ALTER =%
)
call :test ^^^^LF

exit /b

:test
echo(
echo ======== %1 ========

set test=echo Hello%\n%
echo World

set \n
echo(
>test.txt <nul set /p =!\n!
call hexdump test.txt
echo(
set test
echo(
>test.txt <nul set /p =!test!
call hexdump test.txt


echo(
echo -------- %%test%% --------
%test%

echo(
echo -------- (%%test%%) --------
for /f %%A in (".") do (%test%)

exit /b
--OUTPUT--

Code: Select all

======== ^LFLF^ ========
\n=^

^

5E 0A 0A 5E                                    

test=echo Hello
echo World

65 63 68 6F 20 48 65 6C 6C 6F 0A 65 63 68 6F 20
57 6F 72 6C 64                                 

-------- %test% --------
Hello

-------- (%test%) --------
Hello
World

======== ^LF ========
\n=^


5E 0A                                          

test=echo Hello
echo World

65 63 68 6F 20 48 65 6C 6C 6F 0A 65 63 68 6F 20
57 6F 72 6C 64                                 

-------- %test% --------
Hello

-------- (%test%) --------
Hello
World

Dave Benham

Post Reply