Batch file for loop
I am writing a batch file to run on Windows server 2008 R2, it has a for
loop in it and no matter how many tutorials I read online and try, it won't run the for loop.
echo "Starting blur detection..."
if not exist %root%\output mkdir output
set tempnum = dir root | find /i "file"
set num = tempnum-5
for /l %idx in (0, 1, num) do (
%root%\blurDetection.exe %root%\%img%__%idx%.tif %root%\output
ech开发者_JS百科o "blurDetection" %idx " of " %num )
Windows powershell says "idx was unexpected at this time." Any suggestions?
EDIT: Actually I think this line is causing the problem, I don't think it is getting and integer value in return.
set tempnum = dir root | find /i "file"
(added after initial post) Oh and I missed the biggest issue. Which is indeed this line:
set tempnum = dir root | find /i "file"
You mean to capture the output of dir
, right? This can, AFAIK only be done inside for
, unless you can live with outputting into a file and using that as input later.
Syntax is in such a case:
FOR /F ["options"] %variable IN ('command') DO command [command-parameters]
Note: those are not backticks.
Best explanations for NT scripting for
: http://www.robvanderwoude.com/ntfor.php
One more note: since you appear to rely on the file name as some number, I suspect that dir /b
would be the better choice. Plain dir
will also output date and file size etc ...
IIRC names of for
variables cannot have more than one letter. Also, inside script files (as apposed to the command line) those variables look like this:
%%i
Besides, the
set num=tempnum-5
should probably be
set /a num=%tempnum%-5
Another question about find
is whether you meant to use findstr
. Too little context, but findstr
seems more natural.
From for /?
:
FOR %variable IN (set) DO command [command-parameters]
%variable Specifies a single letter replaceable parameter.
(set) Specifies a set of one or more files. Wildcards may be used.
command Specifies the command to carry out for each file.
command-parameters
Specifies parameters or switches for the specified command.
To use the FOR command in a batch program, specify %%variable instead
of %variable. Variable names are case sensitive, so %i is different
from %I.
Note in particular these two statements:
%variable
Specifies a single letter replaceable parameter.- To use the
FOR
command in a batch program, specify%%variable
instead of%variable
.
Another peculiar feature of for loops is that you can split your input and tokens will get assigned to variables in alphabetical order. So inside a for %a ...
tokens would get assigned to %a
, %b
and so forth ...set tempnum = dir root | find /i "file"
Ok, at first glance I can see five six seven (and a half) glaring errors in here:
1. Quotes around arguments to echo
.
echo
is a shell built-in and those had their syntax quite fixed 25 years ago in DOS and CP/M. echo
simply prints whatever comes after it until the end of the command (which could be things like a line break, )
, &
, |
, etc.), so what you want here is:
echo Starting blur detection ...
and
echo blurDetection %i of %num%
2. Spaces around the equals sign in set
.
set
, like echo
is a shell built-in. Its syntax is also a bit awkward (although you have the same issue on some Unix shells as well). The point here is that everything between set
and the equals sign is the variable name, everything after it is the value. In this case this means that your variable name ends with a space and the value starts with a space. Therefore, whatever you do, never put spaces around the equals sign. The language of the command processor is not very forgiving on whitespaces unlike many other programming languages. Therefore:
set foo=bar
and not
set foo = bar
otherwise you will never find a variable named foo
, because you'd need to use %foo %
(note the space in there).
3. set
doesn't do arithmetic unless you tell it to.
set
does not calculate anything unless you explicitly tell it to. This is done with set /a
:
The
/A
switch specifies that the string to the right of the equal sign is a numerical expression that is evaluated.
Therefore you need
set /a num=tempnum - 5
With /a
the %
around variable names can be omitted on the right side which makes the code a little clearer, usually.
4. set
only sets values, it doesn't execute any code.
In
set tempnum=dir root | find /i "file"
you apparently try to set a variable based on the output of some command. This cannot work, as this essentially just sets tempnum
to dir root
and sends the output of that command (there is none) to find
because of the pipe. I'm not quite sure what this is supposed to do, but I'm guessing you're counting files.
Generally, you shouldn't rely on locale-specific output of commands to accomplish something as this breaks fairly easily. Especially when you want to run batch files on other computers than your own (but even in that case you can run into problems). To count files you can iterate over the files and simply count how many you encounter. The for
statement can iterate over files in the following way:
set num=0
for %%f in (%root%\*) do set /a num+=1
Note that I'm assuming there that %root%
is the directory whose files you are interested in. Although you earlier used %root%
. If you're lucky, %root%
is just root
, otherwise your code never had a chance of working correctly.
Since you apparently want to subtract 5 for some files that are not part of what you need, you can do
set /a num-=5
afterwards. Another option, if the files you're interested in share a common trait, would be to restrict the pattern to that, e.g.
for %%f in (%root%\*.tif) do set /a num+=1
so that you don't need to subtract something from the final number.
5. for
variables are single-letter only.
The biggest one here, probably, which was pointed out by STATUS_ACCESS_DENIED already: The variables used in for
statements are special. They are not environment variables and they are only single-letter. The code therefore must read
for /l %%i in ...
6. for
variables must be prefixed by two percent signs in batch files.
On the command line a for
statement like
for /l %x in (1,1,5) do @echo %x
is just fine, but in batch files the parser will stumble over that, probably expecting an argument to the program (which are %1
, %2
, etc.). As the documentation to for
already states, inside of batch files the variables need to use two %
:
for /l %%x in (1,1,5) do @echo %%x
This in turn will break down when used directly on the command line. TANSTAAFL.
However, this does not hold true for environment variables. No doubling of percent signs there (see also below).
Also note that you have another error in the batch in the second-to-last line where you use idx
like an environment variable, even though you try using it as a for
variable elsewhere:
%root%\%img%__%idx%.tif
should read
%root%\%img%__%%i.tif
7. Environment variables must be surrounded by %
.
The line
for /l %i in (0, 1, num) do ...
will never work and probably complain about num
being unexpected. To get the value of an environment variable you need to surround it with percent signs:
for /l %%i in (0, 1, %num%) do ...
otherwise it's just a string.
In a similar vein this also holds for the final echo
. %%i
is a for
variable, therefore only a prefix, but num
is an environment variable so it need to be surrounded by %
:
echo blurDetection %%i of %num%
It can be confusing at times, but luckily we didn't yet look into delayed expansion :-).
And a final one, though minor:
8. Always quote file names.
In your case the whole batch will break down as soon as %root%
contains a path with spaces. This starts in the second line where you look for a directory existing. Make that more robust by enclosing the path in spaces:
if not exist "%root%\output" mkdir output
Same with the invocations inside the loop:
"%root%\blurDetection.exe" "%root%\%img%__%%i.tif" "%root%\output"
You don't break anything when there are no spaces in the file name, but if there are it just works as expected.
Now that we got that out of the way, we can look at how your batch looks now:
echo Starting blur detection ...
if not exist "%root%\output" mkdir "%root%\output%"
set num=0
for %%f in (%root%\*.tif) do set /a num+=1
for /l %%i in (0, 1, %num%) do (
"%root%\blurDetection.exe" "%root%\%img%__%%i.tif" "%root%\output"
echo blurDetection %%i of %num%
)
This should at least work and is probably a fairly faithful rendition of what you thought of, which I'd summarize as:
- Create output directory if it doesn't yet exist
- Find the number of images to process (which are named sequentially)
- Process them and print status output.
But we are not done yet. As you noticed in above explanations, for
can already iterate over files. With step 2 and 3 we are iterating over the set of images twice which isn't exactly needed. All it takes is a single loop and some adaptions:
for %%f in (%root%\%img%__*.tif) do "%root%\blurDetection.exe" "%%f" "%root%\output"
But now we're missing the status output. And the files no longer are processed in order (which may or may not be a problem; I'm assuming here it isn't). If you don't desperately need a “Processed file of ” message, then the following would do:
for %%f in (%root%\%img%__*.tif) do (
"%root%\blurDetection.exe" "%%f" "%root%\output"
echo blurDetection %%~nf
)
which just outputs the last file name processed, but that will still process images in the order of 1
, 10
, 11
, ..., 19
, 2
, 20
... so the number very likely does not accurately resemble progress. If you need a counter, then you need the loop from earlier again which counts the files. I think I leave this now as it is.
A few things I found curious:
- Neither
root
norimg
are defined anywhere. I'm guessing this is just a snippet from a larger program, but to the reader it isn't nice to stumble over variables that have no visible initialization. - While
%root%
is used in plenty of places, the current working directory seems to be identical (line 2, where you check for%root%\output
existing and if not, create justoutput
. Probably apushd
in the beginning and apopd
in the end would be a cleaner way of handling this. - You claim to quote a PowerShell error message, but that's a
cmd
error message. If you ran this script through PowerShell it'd die on the second line already.
Another thing, since you mentioned this should run on Server 2008 R2: PowerShell is installed there by default and if your execution policy is set appropriately, you can also do this with a PowerShell script:
Write-Host Starting blur detection ...
if (!(Test-Path $root\output)) { New-Item -ItemType Directory $root\output }
$files = Get-ChildItem $root\*.tif
$files | ForEach-Object { $i = 1 } {
& "$root\blurDetection.exe" $_ $root\output
Write-Host blurDetection ($i++) of ($files.Count)
}
精彩评论