23

I've been programming in dozens of languages for 20 years but I could never understand how "FOR" work in windows cmd shell batch file, no matter how hard I tried. I read

http://www.amazon.com/Windows-Administration-Command-Line-Vista/dp/0470046163/ref=sr_1_1?ie=UTF8&s=books&qid=1241362727&sr=8-1

http://www.ss64.com/nt/for.html

and several other articles on the internet but still confuse and cannot accomplish anything.

Can anybody give me a concise explaination on how "FOR" works in general ?

For a little more specific question, how can I iterate through each path in %PATH% variable ? I've tried with

rem showpathenv.bat
for /f "delims=;" %%g in ("%PATH%") do echo %%g

That'd show only the first path, not all of them. Why ? What I do wrong ?

Sake
  • 4,033
  • 6
  • 33
  • 37
  • 1
    the 'accepted' answer does miss some important features available for the `for /f` loop. See my own answer... I think it gets much closer to what you asked for than Mark's. But then, I'm not 100% sure what you asked for ;-) – Kurt Pfeifle Aug 07 '10 at 22:14
  • 2
    Nobody answered your question: *'Why ? What I do wrong ?'* -- So I'll try now: It only shows you the first path, because you did only ask for it. It works as designed (I don't say the design is good). You have to ask for more, if you want more: after 'g' comes 'h' in the alphabet -- so add `echo %%h & echo %%i & echo %%j` to see 3 more path directories. – Kurt Pfeifle Aug 07 '10 at 22:20
  • @pipitas and tokens=... C:\>for /f "tokens=1,2,3,4 delims=;" %g in ("%PATH%") do echo %g %h %i – barlop Sep 19 '11 at 22:14
  • @pipitas sake's answer looks quite flexible though, doesn't need a long list of tokens, makes use of substitution (see set /?), to process the path. – barlop Sep 19 '11 at 23:14
  • My advice to whoever come to this question -- Life is short, better just learn Powershell. :) – Sake Dec 09 '22 at 13:10

12 Answers12

12

None of the answers actually work. I've managed to find the solution myself. This is a bit hackish, but it solve the problem for me:

echo off
setlocal enableextensions
setlocal enabledelayedexpansion
set MAX_TRIES=100
set P=%PATH%
for /L %%a in (1, 1, %MAX_TRIES%) do (
  for /F "delims=;" %%g in ("!P!") do (
    echo %%g
    set P=!P:%%g;=!
    if "!P!" == "%%g" goto :eof
  )
)

Oh ! I hate batch file programming !!

Updated

Mark's solution is simpler but it won't work with path containing whitespace. This is a little-modified version of Mark's solution

echo off
setlocal enabledelayedexpansion
set NonBlankPath=%PATH: =#%
set TabbedPath=%NonBlankPath:;= %
for %%g in (%TabbedPath%) do (
  set GG=%%g
  echo !GG:#= !
)
Sake
  • 4,033
  • 6
  • 33
  • 37
  • 1
    that's brilliant.. it seems to have gone unnoticed. You should accept your own answer. Very innovative, to use 2 loops, one as counter, and a second using substitution to go through the path replacing processed bits with nothing, as an alternative to tokens=1,2,3,4.... very nice. – barlop Sep 19 '11 at 23:46
  • Thanks, barlop. Yes this is the best answer, but I feel ashame to accept my own one :). It's amazing that you can understand the code. I can no longer understand what I've done myself. :) – Sake Sep 21 '11 at 04:27
  • Accept your own because it's the best and an accepted answer is more for the database than for any one individual. – barlop Sep 21 '11 at 19:22
  • Funnily enough I just had to make a bat to echo every DIR in the path.. wasn't sure how but checked your one. did it so I could see if there were duplicates. I made use of your technique. I did this (set a=%PATH: =#%) & (set b=%a:;= %) & for %f in (%b%) do @echo %f Didn't mind that each Dir with a space had a #, for my case. Then I did | SORT. – barlop Sep 28 '11 at 01:19
  • 2
    actually looking through there are a few good answers. Look at this from Jay's answer it can be done in one line C:\>for %p in ("%path:;=" "%") do @echo %~p – barlop Sep 28 '11 at 06:24
5

Mark's idea was good, but maybe forgot some path have spaces in them. Replacing ';' with '" "' instead would cut all paths into quoted strings.

set _path="%PATH:;=" "%"
for %%p in (%_path%) do if not "%%~p"=="" echo %%~p

So here, you have your paths displayed.

FOR command in cmd has a tedious learning curve, notably because how variables react within ()'s statements... you can assign any variables, but you can't read then back within the ()'s, unless you use the "setlocal ENABLEDELAYEDEXPANSION" statement, and therefore also use the variables with !!'s instead of %%'s (!_var!)

I currently exclusively script with cmd, for work, had to learn all this :)

Jay
  • 1,635
  • 16
  • 14
  • 1
    Adding an @ after `do` will make sure the command doesn't echo to the command line. And if you change `echo %%~p` to `echo.%%~p`, you can eliminate the "if not" portion. Hence, the second command can be shortened to `for %%p in (%_path%) do @echo.%%~p` – trlkly Aug 22 '14 at 13:39
  • You are right, but the "@" should only be useful on the command-line, as a script should start with @echo off. My loop using two "%%" is a sign it's intended as a script. I never thought of using the "echo." but only for an empty line; however in the current scenario it will echo of blank line, which is something I don't want. Upvoted for the idea tho. – Jay Aug 25 '14 at 14:53
4

Couldn't resist throwing this out there, old as this thread is... Usually when the need arises to iterate through each of the files in PATH, all you really want to do is find a particular file... If that's the case, this one-liner will spit out the first directory it finds your file in:

(ex: looking for java.exe)

for %%x in (java.exe) do echo %%~dp$PATH:x
Tikonu
  • 41
  • 1
4

You've got the right idea, but for /f is designed to work on multi-line files or commands, not individual strings.

In its simplest form, for is like Perl's for, or every other language's foreach. You pass it a list of tokens, and it iterates over them, calling the same command each time.

for %a in (hello world) do @echo %a

The extensions merely provide automatic ways of building the list of tokens. The reason your current code is coming up with nothing is that ';' is the default end of line (comment) symbol. But even if you change that, you'd have to use %%g, %%h, %%i, ... to access the individual tokens, which will severely limit your batch file.

The closest you can get to what you ask for is:

set TabbedPath=%PATH:;= %
for %%g in (%TabbedPath%) do echo %%g

But that will fail for quoted paths that contain semicolons.

In my experience, for /l and for /r are good for extending existing commands, but otherwise for is extremely limited. You can make it slightly more powerful (and confusing) with delayed variable expansion (cmd /v:on), but it's really only good for lists of filenames.

I'd suggest using WSH or PowerShell if you need to perform string manipulation. If you're trying to write whereis for Windows, try where /?.

Mark
  • 6,269
  • 2
  • 35
  • 34
  • Probably, I have too high expectation for batch file. Thanks for the answer. I may not interested in WSH or PowerShell, since I already do Python. – Sake May 03 '09 at 23:08
  • I came up with the solution based on your guide. See my belowed answer – Sake May 04 '09 at 06:55
  • You say *'[...] `for /f` [...] not individual strings'*. My example from today shows how you can dissect an individual string, as long as it has usable delimiters inside. – Kurt Pfeifle Aug 07 '10 at 22:12
  • pipitas: sure, that's what my TabbedPath bit was getting at. But you can't rely on there being usable delimiters. – Mark Aug 09 '10 at 11:49
  • "In its simplest form, for is like Perl's for, or every other language's foreach. You pass it a list of tokens, and it iterates over them, calling the same command each time." Are you sure? It looks like it doesn't iterate. for /F "tokens=1,2,3,4 delims=." %f in ("127.0.0.1") do (echo asdf %f %g %h %i) It only echos asdf once. So it's not like a foreach – barlop Sep 21 '11 at 23:28
  • /f is not its simplest form. You're right, with /f, it iterates over lines rather than tokens. – Mark Sep 25 '11 at 18:53
3

I know this is SUPER old... but just for fun I decided to give this a try:

@Echo OFF
setlocal
set testpath=%path: =#%
FOR /F "tokens=* delims=;" %%P in ("%testpath%") do call :loop %%P
:loop
if '%1'=='' goto endloop
set testpath=%1
set testpath=%testpath:#= %
echo %testpath%
SHIFT
goto :loop
:endloop
pause
endlocal
exit

This doesn't require a count and will go until it finishes. I had the same problem with spaces but it made it through the entire variable. The key to this is the loop labels and the SHIFT function.

iesou
  • 706
  • 5
  • 13
2

This works for me:

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
@REM insure path is terminated with a ;
set tpath=%path%;
echo.
:again
@REM This FOR statement grabs the first element in the path
FOR /F "delims=;" %%I IN ("%TPATH%") DO (
  echo    %%I 
  @REM remove the current element of the path
  set TPATH=!TPATH:%%I;=!
)
@REM loop back if there is more to do. 
IF DEFINED TPATH GOTO :again

ENDLOCAL
Cheeso
  • 189,189
  • 101
  • 473
  • 713
2

You have to additionally use the tokens=1,2,... part of the options that the for loop allows. This here will do what you possibly want:

for /f "tokens=1,2,3,4,5,6,7,8,9,10,11,12 delims=;" %a in ("%PATH%") ^
do ( ^
     echo. %b ^
   & echo. %a ^
   & echo. %c ^
   & echo. %d ^
   & echo. %e ^
   & echo. %f ^
   & echo. %g ^
   & echo. %h ^
   & echo. %i ^
   & echo. %j ^
   & echo. %k ^
   & echo. ^
   & echo.   ...and now for some more... ^
   & echo. ^
   & echo. %a ^| %b ___ %c ... %d ^
   & dir "%e" ^
   & cd "%f" ^
   & dir /tw "%g" ^
   & echo. "%h  %i  %j  %k" ^
   & cacls "%f")

This example processes the first 12 tokens (=directories from %path%) only. It uses explicit enumeration of each of the used tokens. Note, that the token names are case sensitive: %a is different from %A.

To be save for paths with spaces, surround all %x with quotes like this "%i". I didn't do it here where I'm only echoing the tokens.

You could also do s.th. like this:

for /f "tokens=1,3,5,7-26* delims=;" %a in ("%PATH%") ^
do ( ^
     echo. %c ^
   & echo. %b ^
   & echo. %a ^
   & echo. %d ^
   & echo. %e ^
   & echo. %f ^
   & echo. %g ^
   & echo. %h ^
   & echo. %i ^
   & echo. %j ^
   & echo. %k )

This one skips tokens 2,4,6 and uses a little shortcut ("7-26") to name the rest of them. Note how %c, %b, %a are processed in reverse order this time, and how they now 'mean' different tokens, compared to the first example.

So this surely isn't the concise explanation you asked for. But maybe the examples help to clarify a little better now...

Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
  • Thanks for more explanation. Honestly, after a year, I barely understand batch-file now. I wonder what's the point of for-loop if we have to work on different variables for the values in the same series ? – Sake Aug 08 '10 at 01:10
  • 1
    @Sake: A `for` loop in this context splits the input ("`%PATH%`") into different lines. The tokens split each line into different chunks (boundaries depend on the given delimiters). Now, in our case, we have only 1 single line of input. (But the loop would work with multiple lines equally well.) We use the for loop, because we don't have other means to split lines (be it a single, or being it multiple ones) into chunks. – Kurt Pfeifle Aug 17 '10 at 08:33
  • that example is somewhat long. you could've demonstrated it with a shorter example, like one line. rather than an example where one might fall asleep typing it in, then wake up, type some more in, have some dinner, make a typo on one line, get mad, start again, and quit. – barlop Sep 19 '11 at 22:09
  • @barlop: I think the answer had exactly the right length for someone knowing a dozen programming languages and saying he already spent 20 years in vain for understanding it.... ;-P – Kurt Pfeifle Sep 21 '11 at 05:47
  • 1
    @barlop: ...also, putting it into a single line would make it much less readable. I'm glad you fall asleep so easily, despite of the world being such an evil place. – Kurt Pfeifle Sep 21 '11 at 05:48
  • @pipitas for illustrating a concept, i.e. with an example, it can be a short line. In implementation, he wants to work with %PATH% so that would be long, but an example to illustrate the concept with the for loop, need not use %PATH%. – barlop Sep 21 '11 at 19:07
  • @Sake "I wonder what's the point of for-loop if we have to work on different variables for the values in the same series ?" Besides iteration, You have to use different variables Dir1 Dir2 Dir3 (and can in a loop Dir%i ) e.t.c. or alternatively a loop through %f %g %h %i Because batch files don't include an Array structure. And a loop still works for looping through all those variables. Or as you did, 2 loops one var. one loop just processes the first element and removes the first element, and an outter loop repeating the inner loop until around when the string is empty or has one left. – barlop Sep 21 '11 at 19:16
  • @Sake "if we have to work on different variables for the values" of course.. you managed it without doing that! – barlop Sep 21 '11 at 19:21
  • To correct myself, it looks like for /f with "Tokens.." doesn't iterate...infact, I mean, it scans(I don't think one would say iterates there) through the string but doesn't iterate the body – barlop Sep 21 '11 at 23:26
2

for /f iterates per line input, so in your program will only output first path.

your program treats %PATH% as one-line input, and deliminate by ;, put first result to %%g, then output %%g (first deliminated path).

Francis
  • 11,388
  • 2
  • 33
  • 37
  • Note however, that this will fail if the first path has a semicolon in it. The tokenizing done by for in this case will not handle quoting. – Joey May 03 '09 at 15:27
2

FOR is essentially iterating over the "lines" in the data set. In this case, there is one line that contains the path. The "delims=;" is just telling it to separate on semi-colons. If you change the body to echo %%g,%%h,%%i,%%j,%%k you'll see that it is treating the input as a single line and breaking it into multiple tokens.

D.Shawley
  • 58,213
  • 10
  • 98
  • 113
1

Here is a good guide:

FOR /F - Loop command: against a set of files.

FOR /F - Loop command: against the results of another command.

FOR - Loop command: all options Files, Directory, List.

[The whole guide (Windows XP commands):

http://www.ss64.com/nt/index.html

Edit: Sorry, didn't see that the link was already in the OP, as it appeared to me as a part of the Amazon link.

TFM
  • 544
  • 5
  • 11
0

It works for me, try it.

for /f "delims=;" %g in ('echo %PATH%') do echo %g%
BerY
  • 13
  • 3
-2

It works for me, try it.

for /f "tokens=* delims=;" %g in ('echo %PATH%') do echo %g%

  • Welcome to Stack Overflow! Whilst this code snippet is welcome, and may provide some help, it would be [greatly improved if it included an explanation](//meta.stackexchange.com/q/114762) of *how* and *why* this solves the problem. Remember that you are answering the question for readers in the future, not just the person asking now! Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Apr 06 '17 at 17:16