CSG
CSG

Reputation: 277

for-loop and delimeters

I need e fast way to insert a few rows of structured data in a bat file I've used an array called myarray to scan and "read" my values but it does not work and i don't understand why This is my code:

@echo off
set myarray[1]=myfield1#myfield2#mysubfield31;mysubfield32#myfield4
for /f "tokens=1-9 delims=#" %%a in ('echo %myarray[1]%') do (
    echo field1 is %%a
    echo field2 is %%b
    echo field3 is %%c
    echo field4 is %%d
    for /f "tokens=1-9 delims=;" %%k in ('echo %%c') do (
        echo subfield3 is %%k
        echo subfield3 is %%l       
    )
)

the output is like this:

field1 is myfield1
field2 is myfield2
field3 is mysubfield31 mysubfield32
field4 is myfield4
subfield3 is mysubfield31 mysubfield32
subfield3 is

why i can't obtain simply:

subfield3 is mysubfield31 
subfield3 is mysubfield32

where is the ";" used as delimeter in the second for ?

Upvotes: 1

Views: 646

Answers (3)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200293

That's documented behavior, the semicolon is treated as whitespace (not by the for loop, but by the subshell executing the echo command). This causes the semicolon to be replaced with a space, so that echo field3 is %%c produces the output field3 is mysubfield31 mysubfield32. Either remove delims=; from the inner loop, so it uses the default delimiters (space and tab), or choose a different delimiter character, and your script should work as expected. Note, however, that relying on the default delimiters in the inner loop may produce undesired results, when fields of the nested "array" contain spaces.

Proof:

  1. Demonstration of the root cause:

    for /f "tokens=*" %%a in ('echo.a#b;c#d^|find ";"') do echo _%%a_
    

    Output: none (i.e. no semicolon found in the echoed string)

    for /f "tokens=*" %%a in ('echo.a#b;c#d^|find " "') do echo _%%a_
    

    Output: _a#b c#d_

    Apparently this applies to all delimiter characters (,, ;, =, space and tab), since I could reproduce this behavior with each of them.

  2. Simplified script with an input string a#b;c#d:

    @echo off
    set "foo=a#b;c#d"
    for /f "tokens=1-3 delims=#" %%a in ('echo %foo%') do (
      echo field1 is %%a
      echo field2 is %%b
      echo field3 is %%c
      for /f "tokens=1-2 delims=;" %%k in ('echo %%b') do (
        echo subfield3 is %%k
        echo subfield3 is %%l       
      )
    )
    

    Output:

    field1 is a
    field2 is b c
    field3 is d
    subfield3 is b c
    subfield3 is

    Note the space between b and c in 2nd and 4th line of the output.

  3. Same script as 2., but with delims=; removed from the inner loop:

    @echo off
    set "foo=a#b;c#d"
    for /f "tokens=1-3 delims=#" %%a in ('echo %foo%') do (
      echo field1 is %%a
      echo field2 is %%b
      echo field3 is %%c
      for /f "tokens=1-2" %%k in ('echo %%b') do (
        echo subfield3 is %%k
        echo subfield3 is %%l       
      )
    )
    

    Output:

    field1 is a
    field2 is b c
    field3 is d
    subfield3 is b
    subfield3 is c

    Note that the nested "array" b;c (from the variable %foo%) is now processed into the correct output (lines 4 and 5). This is, because spaces and tabs are the default delimiters in for /f loops.

  4. Same script as 2., but using + as the delimiter for the nested "array":

    @echo off
    set "foo=a#b+c#d"
    for /f "tokens=1-3 delims=#" %%a in ('echo %foo%') do (
      echo field1 is %%a
      echo field2 is %%b
      echo field3 is %%c
      for /f "tokens=1-2 delims=+" %%k in ('echo %%b') do (
        echo subfield3 is %%k
        echo subfield3 is %%l       
      )
    )
    

    Output:

    field1 is a
    field2 is b+c
    field3 is d
    subfield3 is b
    subfield3 is c

    Note that the 2nd output line no longer contains the bogus space, and that, again, the nested "array" is processed correctly (lines 4 and 5).


For completeness: a better solution would be to drop the echo altogether and simply loop on a double quoted string, as foxidrive and Peter Wright suggested, because it avoids the issue entirely.

@echo off
set "foo=a#b;c#d"
for /f "tokens=1-3 delims=#" %%a in ("%foo%") do (
  echo field1 is %%a
  echo field2 is %%b
  echo field3 is %%c
  for /f "tokens=1-2 delims=;" %%k in ("%%b") do (
    echo subfield3 is %%k
    echo subfield3 is %%l       
  )
)

Another solution, proposed by dbenham, would be to enable delayed expansion and have the variable expanded at runtime:

@echo off
setlocal EnableDelayedExpansion
set "foo=a#b;c#d"
for /f "tokens=1-3 delims=#" %%a in ('echo !foo!') do (
  echo field1 is %%a
  echo field2 is %%b
  echo field3 is %%c
  for /f "tokens=1-2 delims=;" %%k in ('echo %%b') do (
    echo subfield3 is %%k
    echo subfield3 is %%l       
  )
)

An even better solution, however, would be to drop batch entirely and switch to a language that actually supports arrays, e.g. PowerShell.

Upvotes: -1

foxidrive
foxidrive

Reputation: 41234

This works:

@echo off
set myarray[1]=myfield1#myfield2#mysubfield31;mysubfield32#myfield4
for /f "tokens=1-9 delims=#" %%a in ("%myarray[1]%") do (
    echo field1 is %%a
    echo field2 is %%b
    echo field3 is %%c
    echo field4 is %%d
    for /f "tokens=1-9 delims=;" %%k in ("%%c") do (
        echo subfield3 is %%k
        echo subfield3 is %%l       
    )
)
pause

Upvotes: 5

Magoo
Magoo

Reputation: 80033

for /f "tokens=1-9 delims=#" %%a in ("%myarray[1]%") do (

may work a tad better...

Upvotes: 1

Related Questions