I’ve been talking about the powerful For tool over the
past couple months. For can inject power into any
command-line utility, letting it transform a command
that normally operates on a single file into one that can
work on many files. In the first For column, “The Power of
For” (InstantDoc ID 96539), I discussed how For can turn
a command loose on an entire folder’s worth of files, and
in the second column, “Counting on For” (InstantDoc ID
96704), I showed you how For’s /l option lets you instruct a
command to run any number of times.
This month, I show you how For’s /f option lets you tell
Windows to apply a single command to a specific list of files.
For for Files
This For functionality came to mind a few weeks ago while
I was reviewing the results of a photo shoot. I’d been snapping
dozens of close-up photos of a Snowy Egret from a
photographer’s blind. I had some good, detailed shots,
but most didn’t amount to much. I wanted to burn all the
photos to a DVD but keep the most useful ones on my
computer’s hard disk.
You’d think separating out a few pictures would be
simple—say, by browsing the pictures in one window while
dragging the good ones to another folder. But I needed
to devote a lot of screen real estate to the image browser
and didn’t have enough screen space to hold a couple of
Windows Explorer windows on top of that. But I did have
enough space for a little Notepad window, in which I could
type the names of the files I wanted to keep, leaving me lots
of room for the image browser.
I had a folder full of files called C:\newpics, and I had
created a text file named keepers.txt that listed the photos I
wanted to copy to a folder called C:\goodpics. I wanted to
extract each line in keepers.txt and use it as a filename to
copy to C:\goodpics. How could I use the Windows command
line to accomplish that goal?
I was sure the answer lay in the For command, so I dived
into For’s online Help, which reminded me of the tool’s /f
option. Here’s the command I came up with:
for /f %i in (C:\newpics\keepers.txt) do copy C:\newpics\%i C:\goodpics
To understand this command, look at the simplified For
/f syntax:
for /f <variable> in (<name of file listing the
desired objects>) do <an operation involving the
variable>
For example, to tell For /f to simply display the files that it
will copy, I could type
for /f %i in (C:\newpics\keepers.txt) do echo %i
For /f works its way through keepers.txt by taking one line
at a time and putting the contents of that line in a variable,
a place in memory that I’ve called “%i.” (Any name works,
as long as it’s prefixed with a percent sign.) Then, For /f
performs whatever action you’ve typed to the right of do,
replacing the two letters “%i” with the actual value that For
has most recently extracted from keepers.txt.
Thus, if I type pic1.cr2 on the first line, and pic7.cr2 on
the second and final line, For /f would first execute echo
pic1.cr2, which would cause Windows to just print pic1.
cr2 on the command window, then print pic7.cr2 on that
window and stop.
That’s not all For /f can do. Instead of putting a file
in the parentheses, you can put a command in there,
surrounded by single quotes. For will then execute the
command and use each line of the command’s output as
a line of text to operate on, just as it operated on the lines
in keepers.txt.
For can also accept more than one file as input in the
parentheses, as in a variation on the first example, featuring
both the file keepers.txt and another named keepers2.
txt:
for /f %i in (C:\newpics\keepers.txt C:\newpics\keepers2.txt) do copy C:\newpics\%i C:\goodpics
More to For
There’s more to For, of course. But I think these three visits
with “the original Windows power tool” should give you a
pretty good starting point toward your own For experimentation.
If you learn only one new command-line tool this
year, make it For!
End of Article
For instance, go into "C:\Program Files" and run:
for /f %i in ('dir /b') do @echo %i
You'll see that the output is probably not what is desired. Instead use:
for /f "delims=" %i in ('dir /b') do @echo %i
Here's another trick. findstr /s is very inefficient at directory recursion. Use the following for better performance:
for /f "delims=" %i in ('dir /s /a /b *foo*.bar') do @ findstr text "%i*"
The performance tradeoffs depend upon how many files are being searched and how many files are not beind searched - if there are a lot of files to search, than the overhead of a separate findstr process for each file overwhelms the benefits of using dir /s instead of findstr /s. But if there only a few files in which to search, but many files in the directory tree, than the advantages of using dir /s to hunt the tree can be large.
I use the "%i*" syntax to ensure that findstr outputs the filename as part of it's output. One disadvantage of that is that if there are other files that match (i.e. foo.txt vs foo.txt_old), you'll match extraneous files, but there are tradeoffs everywhere.
Using the /F option can be very handy in conjunction with dir /b - I'll do things like using 'dir /s /ad /b' (i.e. do the command once for each directory in the tree), etc. Sometimes I even set up for to call for (use a different variable for the second level for). And I'm a hard-core Perl programmer, so it's not like I can't do this stuff in another tool. Also keep an eye on %~ni and things like that - it's wicked what you can work up!
One last hint - wrap an entire for line in () before redirecting output to a file!
--Toby Ovod-Everett
tovod-everett November 28, 2007 (Article Rating: