开发者

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:

  1. Create output directory if it doesn't yet exist
  2. Find the number of images to process (which are named sequentially)
  3. 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 nor img 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 just output. Probably a pushd in the beginning and a popd 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)
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜