2

I want to read a string containing several(number unterminated) hosts of Kafka and list them in separate lines with cmd for.

The string is like:

host1:9092,host2:9092,host3:9092,...

And what I have is:

set /p BOOTSTRAPSERVERS="Bootstrap servers with port, separated by comma(by default localhost:9092): "
REM echo "%BOOTSTRAPSERVERS%"
if "%BOOTSTRAPSERVERS%"=="" set BOOTSTRAPSERVERS="localhost"

echo All the servers to connect: 
FOR /F "usebackq delims=," %%i in (`echo %BOOTSTRAPSERVERS%`) do (
    echo %%i
    echo.
)

But, I only get all the hosts in one line, space separated:

All the servers to connect:
host1:9092 host2:9092 host3:9092

Why?

If I turn on @echo on, I see this, which does not make sense:

set /p BOOTSTRAPSERVERS="Bootstrap servers with port, separated by comma(by default localhost:9092): "
Bootstrap servers with port, separated by comma(by default localhost:9092): host1:9092,host2:9092,host3:9092

REM echo "host1:9092,host2:9092,host3:9092"

if "host1:9092,host2:9092,host3:9092" == "" set BOOTSTRAPSERVERS="localhost"

echo All the servers to connect:
All the servers to connect:

FOR /F "usebackq delims=," %i in (`echo host1:9092 host2:9092 host3:9092`) do (
echo %i
 echo.
)

(
echo host1:9092
 echo.
)
host1:9092

If you observe carefully, echo turns the string from comma separated into space separated, when %BOOTSTRAPSERVERS% is not double quoted.

If it is double quoted, the output will be:

"host1:9092

Why?

Is there a better way to split a string with delimiters?

WesternGun
  • 11,303
  • 6
  • 88
  • 157
  • Why are you using `echo` after all, why not this: `for /F "delims=," %%I in ("%BOOTSTRAPSERVERS%") do echo(%%I`? I think the `cmd.exe` instance that executed the `echo` inside your `for /F` loop considers `,` as token separators and replaces them by space... – aschipfl Jan 16 '20 at 15:45
  • No, that does not work; I get `host1:9092` – WesternGun Jan 16 '20 at 15:50
  • The problem is that without `usebackq` I can use `"%var%"` to parse this var string, but then I don't know how many hosts I can get; so I can print `%%I`, which is the first, but the second, the third would be `%%J`, `%%K`, and more. – WesternGun Jan 16 '20 at 16:26

2 Answers2

3

To split a string by standard delimiters (SPACE, TAB, Comma), you can use a plain for loop (no /f):

set "bootstrapservers=host1:9092,host2:9092,host3:9092"
for %%a in (%bootstrapservers%) do echo %%a

Output:

host1:9092
host2:9092
host3:9092

Note the syntax for the set command (this way the quotes don't become part of the value). Same syntax works with set /p: set /p "var=Prompt:"

Stephan
  • 53,940
  • 10
  • 58
  • 91
  • Ah.. so if the separator is among space, `tab` and `,`, I don't need `for /f`? Good to know. Don't notice this though in `for /?` – WesternGun Jan 16 '20 at 16:46
  • 1
    Not quite. `for` and `for /f` are working complete different. `for /f` loops over line(s), tokenizing its string(s); `for` loops over every component of a list. As a result, `for /f` with a single string is looping only once, `for` is looping for each component (three times for this example). See `for /?` or [SS64](https://ss64.com/nt/) for a more explanative text. (or run with `echo on` and find out yourself) – Stephan Jan 16 '20 at 16:54
2

The token mechanism of FOR /F works on a line basis.
Per input line (lines are separated by line feeds) tokens are build by the options delims and tokens.

delims=... defines how the text is split into the tokens.
tokens=... defines how many tokens you want to create, the first token is stored in the FOR parameter variable, the next tokens are stored in the next parameter character by alphabetical order.

In this sample the tokens %%A %%B %%C are used to store 1, 2, 3

FOR /F "tokens=1,2,3" %%A ("1 2 3 4 5") DO ...

In your case you probably want to get two tokens for host: and value.
But you currently have only one line, that wouldn't work with FOR /F.

But you can replace each , with a linefeed character LF, the replace is done with !hosts:,=%%~L!
Then the FOR /F gets multiple lines like:

host1:9092
host2:9092
host3:9092
host4:4711

These lines can be splitted by a delimiter of :

@echo off
setlocal EnableDelayedExpansion

set "hosts=host1:9092,host2:9092,host3:9092,host4:4711"
(set LF=^
%=empty line=%
)

FOR %%L in ("!LF!") DO (
    FOR /F "tokens=1,2 delims=:" %%1 in ("!hosts:,=%%~L!") DO (
        echo Host: %%1 Value: %%2
    )
)
jeb
  • 78,592
  • 17
  • 171
  • 225
  • Nah it's ok just splitting with `,` because 9092 is a port which I will later contain in the server address like `host:port`. I tried to understand the orig version but I don't get it; about `set LF=...` part neither `"!hosts:,=%%~L!"` part. Though I get what you means roughly. – WesternGun Jan 16 '20 at 15:30
  • 1
    The `set LF=^...` part is the [batch way to create a variable containing a single linefeed](https://stackoverflow.com/a/6379940/463115). `!hosts:,=%%~L!` part used the variable replace syntax (see `set /?`) `!variablename:=!` – jeb Jan 16 '20 at 15:38
  • In the first outer loop, you try to for loop in the empty line? I don't get it; I thought I could search in one line; the example in `for /?` shows that it could iterate lines in a file, and split each line with a delimiter to search a word, so how is it not possible in one line....? I mean, sth like `for /r %G in (*.txt) do ( for /f "tokens=*" %H do (findstr /C:string /IMN %H) )` – WesternGun Jan 16 '20 at 15:41
  • 1
    Okay the outer loop is a hack. It's necessary to get the LF/linefeed into a FOR-parameter. That's necessary to use it later as `%%~L` in the search/replace expression of `!hosts:,=%%~L!` – jeb Jan 16 '20 at 15:44