1

我是 dos 批处理脚本的新手。我拼凑了几个代码片段,并进行了一些修改,让我的脚本适用于一个小目录。它通过目录树递归,计算作为参数传入的日期范围内的文件数,然后写入输出文件报告。

它适用于小型目录结构,但如果它必须递归遍历数百个文件夹,则它会因“批量递归超出堆栈限制”错误而中止。

我从这个站点了解到递归循环不是很有效,一旦我在堆栈上放置了一定数量的数据,我就会干杯。我已经在这个网站和其他地方寻求帮助。大多数建议是编写一个更高效的程序,但我不确定如何做到这一点。任何帮助,将不胜感激。我需要将效率提高一个数量级,因为我需要它的目录结构将有数千个文件夹。这是我的代码:

@echo off
setlocal enableDelayedExpansion
pushd %1

REM This program takes three parameters <starting directory> <startdate> <enddate>
REM The startdate and endate should be in format: mm/dd/yyyy
REM The program will recursively look through all directories and sub-directories
REM from the given <starting directory> and count up the number of files written
REM within the date range from <startdate> until <endate>
REM It will then write out a RetentionReport_<date>_<time>.txt file that lists
REM one line showing the <startdate> <enddate> and the # of files found.

REM If you don't pass in all three arguments it will let you know and then exit.

REM You need to set your TESTDIR below to a hardpath location for the writing
REM of temporary files and writing of the Reports

REM There is one .tmp file created during processing and then deleted.
REM To prevent the .tmp file being deleted you can comment out the second
REM instance of this line by adding a REM in front of it:
REM if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp

REM If you want to print out a .tmp file that lists the files counted for the
REM period given then you could remove the REM from in front of the following
REM line in the below code: echo %%~tF   %%F>> %TESTDIR%MONTH_files.tmp


set "TAB=   "
set "MONTHTOTAL=0"
set hr=%time:~0,2%
if "%hr:~0,1%" equ " " set hr=0%hr:~1,1%
set "TESTDIR=C:\TEST\"

if "%~2" == "" (
    echo Please pass in arguments for starting directory, startdate, and enddate.
    echo startdate and endate should be in the format mm/dd/yyyy
    exit/b
)
if "%~3" == "" (
    echo Please pass in arguments for starting directory, startdate, and enddate.
    echo startdate and endate should be in the format mm/dd/yyyy
    exit/b
)


set "startdate=%~2"
set "enddate=%~3"

if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
call :run >nul
FOR /F %%i IN (%TESTDIR%temp_TotalByDir.tmp) DO set /a MONTHTOTAL=!MONTHTOTAL!+%%i
echo %startdate%%TAB%%enddate%%TAB%!MONTHTOTAL! >> %TESTDIR%RetentionReport_%date:~-    4,4%%date:~-10,2%%date:~-7,2%_%hr%%time:~3,2%%time:~6,2%.txt
if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
exit /b

:run
for %%F in (.) do echo %%~fF
endlocal

:listFolder
setlocal enableDelayedExpansion
set "ADD=0"

for %%F in (*) do ( 
  if %%~tF GEQ %startdate% (
      if %%~tF LEQ %enddate% (
          REM echo %%~tF   %%F>> %TESTDIR%MONTH_files.tmp
      set /a ADD+=1
      )
  )
)

echo !ADD! >> %TESTDIR%temp_TotalByDir.tmp

for /d %%F in (*) do (
  pushd "%%F"
  call :listFolder
  popd
)

endlocal

exit /b

在此先感谢您的帮助!!!

4

3 回答 3

0

文件夹的数量与递归级别无关。如果使用递归调用处理每个目录级别,则递归调用的最大深度在一个非常大的树中应该是 10 或 11。我建议您从下面的代码开始,并根据您的需要对其进行修改:

@echo off
call :treeProcess
goto :eof

:treeProcess
rem Do whatever you want here over the files of this subdir, for example:
copy *.* C:\dest\dir
for /D %%d in (*) do (
    cd %%d
    call :treeProcess
    cd ..
)
exit /b

编辑

我查看了您的代码,唯一奇怪的是endlocal放置在:run标签下方的命令。我认为此时所有先前定义的变量都被释放,结果无法预测。这是您的代码的一个更简单的版本,我认为它应该可以正常工作:

@echo off
setlocal

set "TAB=   "
set "MONTHTOTAL=0"
set hr=%time:~0,2%
if "%hr:~0,1%" equ " " set hr=0%hr:~1,1%
set "TESTDIR=C:\TEST\"

set "startdate=%~2"
set "enddate=%~3"

if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
call :run >nul
FOR /F %%i IN (%TESTDIR%temp_TotalByDir.tmp) DO set /a MONTHTOTAL+=%%i
echo %startdate%%TAB%%enddate%%TAB%%MONTHTOTAL% >> %TESTDIR%RetentionReport_%date:~-4,4%%date:~-10,2%%date:~-7,2%_%hr%%time:~3,2%%time:~6,2%.txt
if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
exit /b

:run

:listFolder

set "ADD=0"

for %%F in (*) do ( 
   if %%~tF GEQ %startdate% (
      if %%~tF LEQ %enddate% (
         REM echo %%~tF   %%F>> %TESTDIR%MONTH_files.tmp
         set /a ADD+=1
      )
   )
)

echo %ADD% >> %TESTDIR%temp_TotalByDir.tmp

for /d %%F in (*) do (
  cd "%%F"
  call :listFolder
  cd ..
)
exit /b
于 2014-02-28T17:56:46.970 回答
0

你的问题是由调用 inside 引起:listFolder:listFolder

@echo off
setlocal

REM This program takes three parameters <starting directory> <startdate> <enddate>
REM The startdate and endate should be in format: mm/dd/yyyy
REM The program will recursively look through all directories and sub-directories
REM from the given <starting directory> and count up the number of files written
REM within the date range from <startdate> until <endate>
REM It will then write out a RetentionReport_<date>_<time>.txt file that lists
REM one line showing the <startdate> <enddate> and the # of files found.

REM If you don't pass in all three arguments it will let you know and then exit.

REM You need to set your TESTDIR below to a hardpath location for the writing
REM of temporary files and writing of the Reports

REM There is one .tmp file created during processing and then deleted.
REM To prevent the .tmp file being deleted you can comment out the second
REM instance of this line by adding a REM in front of it:
REM if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp

REM If you want to print out a .tmp file that lists the files counted for the
REM period given then you could remove the REM from in front of the following
REM line in the below code: echo %%~tF   %%F>> %TESTDIR%MONTH_files.tmp


set "startdate=%~2"
set "enddate=%~3"
IF DEFINED startdate IF DEFINED enddate GOTO parmsok
echo Please pass in arguments for starting directory, startdate, and enddate.
echo startdate and endate should be in the format mm/dd/yyyy
GOTO :eof

:parmsok
CALL :convdate startdate "%startdate%"
CALL :convdate enddate "%enddate%"
set "TAB=   "
set /a MONTHTOTAL=0
set /a GRANDTOTAL=0
set "TESTDIR=C:\TEST\"

if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
pushd %1
REM call :run >NUL
call :run
popd
FOR /F %%i IN (%TESTDIR%temp_TotalByDir.tmp) DO set /a MONTHTOTAL+=%%i
set hr=%time:~0,2%
set hr=%hr: =0%
echo %startdate%%TAB%%enddate%%TAB%%MONTHTOTAL% >> "%TESTDIR%RetentionReport_%date:~-4,4%%date:~-10,2%%date:~-7,2%_%hr%%time:~3,2%%time:~6,2%.txt"
ECHO %grandtotal%
if exist %TESTDIR%*.tmp del %TESTDIR%*.tmp
GOTO :eof 

:run
for /d /r . %%T in (.) do (
  pushd "%%T"
  IF NOT ERRORLEVEL 1 (
   call :listFolder
   POPD
  )
)
GOTO :eof

:listFolder
set /a ADD=0

for %%F in (*) do (
  CALL :convdate filedate "%%~tF"
  CALL :compdate
  IF DEFINED inrange (
  REM  echo %%~tF   %%F>> %TESTDIR%MONTH_files.txt
    set /a ADD+=1
    SET /a GRANDTOTAL+=1
  )
)
echo %ADD% >> %TESTDIR%temp_TotalByDir.tmp

GOTO :EOF

:: Convert date in %2 to yyyymmdd in %1

:convdate
SET "$1=%~2"
:: replace any space with 0
SET $1=%$1: =0%
:: convert date format. I use dd/mm/yyyy.
SET %1=%$1:~6,4%%$1:~3,2%%$1:~0,2%
:: version for mm/dd/yyyy.
REM SET %1=%$1:~3,2%%$1:~6,4%%$1:~0,2%
GOTO :EOF

:: Set Inrange iff date is in range

:compdate
SET "inrange="
if %filedate% GEQ %startdate% if %filedate% LEQ %enddate% SET inrange=Y
GOTO :eof

有趣的练习。

几点:

不需要延迟扩展。

分配日期范围变量。仅当两者都存在时才继续否则错误消息并退出。

设置公共变量。请注意,该语法set "var=string"旨在确保行上的尾随空格不包含在分配的值中。set /a分配数值的语法不受尾随空格的影响。

PUSHD你的目标目录;POPD 处理后返回您的原件。

>nulcall :run允许 kbd 中断中删除了

语法 `set "var=%var: =0%" 用零替换空格。

我将 的设置移到生成hr之前RetentionReport,以防运行时导致小时更改。

您的原始(可选)文件列表已写入本应.tmp删除的文件,因此我将其更改为.txt文件。

for /d /r执行递归目录名扫描,包括所示形式的当前目录。

如果pushd失败(它在包含!with的目录名上失败delayedexpansion)那么它是无效的,所以只有 iferrorlevel是 0 才应该执行calland popd

listfolder 只检查一个目录 - 当前。

需要将日期转换为 yyyymmdd 形式以进行正确比较。这需要在提供的日期上执行一次(在循环中永久重新转换它们没有意义),这是在开始时完成的。

for 的主要用途delayedexpansion是访问变量的值,其中值在循环中发生变化;但是,if defined测试(andif existif errorlevel n)作用于运行时值,而不是解析时值。因此,这种方法避免使用delayedexpansion- 进行演示,如果没有别的。

最后的评论:xcopy /L /D使用适当的日期执行一个空目录将列出在选定日期和之后生成的文件。因此,XCOPY /L /D可以诱使两个日期运行到一个空目录以产生文件计数;#earlier - #later = #between。

文件(=行)的计数MONTH_files.txt将等于GRANDTOTAL(和) - 但在开始时删除(不再是文件......)MONTHTOTAL是一个好主意MONTH_files.txt.tmp

于 2014-02-28T19:03:36.837 回答
0

除了递归堆栈限制之外,您还有其他问题。IF 语句不知道如何比较日期。它只知道字符串和数字。为了正确比较日期,您必须将日期重新格式化为 YYYYMMDD 格式。

应一次性收集所有用于输出文件名的时间戳信息。您现有的代码在流程开始时获取小时,在流程结束时获取日期、分钟和秒。不好。开始时间和结束时间之间可能存在很大的时间间隔。

Batch 有两种类型的递归错误:

1) 一个 CALL 级别中只有 31 个 SETLOCAL 级别。

2) 允许可变数量的递归调用,具体取决于 Windows 版本、机器内存……

没有办法增加堆栈的大小。如果遇到递归错误,则必须寻找减少递归量的方法。在您的情况下,您可以简单地让 FOR /R 为您完成所有递归!

我修改了脚本,以便在传递第四个参数时生成文件列表。我还将时间戳合并到文件列表文件名中。

该代码假定您机器的文件日期/时间值以 MM/DD/YYYY 开头。如果没有,则必须修改代码。

@echo off
setlocal enableDelayedExpansion

REM This program takes three parameters <starting directory> <startdate> <enddate>
REM The startdate and endate should be in format: mm/dd/yyyy
REM The program will recursively look through all directories and sub-directories
REM from the given <starting directory> and count up the number of files written
REM within the date range from <startdate> until <endate>
REM It will then write out a RetentionReport_<date>_<time>.txt file that lists
REM one line showing the <startdate> <enddate> and the # of files found.

REM If you don't pass in all three arguments it will let you know and then exit.

REM You need to set your TESTDIR below to a hardpath location for the writing
REM of the Reports

REM If you want to print out a .tmp file that lists the files counted for the
REM period given then you can pass in a 4th argument with any value

if "%~3" == "" (
  echo Please pass in arguments for starting directory, startdate, and enddate.
  echo startdate and endate should be in the format mm/dd/yyyy
  exit/b
)

set "TAB=   "
set "TESTDIR=D:\TEST\"

set "startdate=%~2"
set "start=%startdate:~-4%%startdate:~0,2%%startdate:~3,2%"
set "enddate=%~3"
set "end=%enddate:~-4%%enddate:~0,2%%enddate:~3,2%"

set "timestamp=%date:~-4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2%%time:~6,2%"
set "timestamp=%timestamp: =0%"

for /r "%~1" %%F in (*) do (
  set "dt=%%~tF"
  set "dt=!dt:~6,4!!dt:~0,2!!dt:~3,2!"
  if !dt! geq %start% if !dt! leq %end% (
    if "%~4" neq "" (echo %%~tF   %%F) >>"%TESTDIR%MONTH_files_%timestamp%.tmp"
    set /a cnt+=1
  )
)

(echo %startdate%%TAB%%enddate%%TAB%%cnt%) >>"%testdir%RetentionReport_%timestamp%.txt"
于 2014-02-28T20:24:55.237 回答