我是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
先谢谢你的帮助!!!
答案 0 :(得分:0)
文件夹数与递归级别无关。如果使用递归调用处理每个目录级别,则在非常大的树中,递归调用的最大深度应为10或11。我建议您先从the code below开始,然后根据需要进行修改:
@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
答案 1 :(得分:0)
问题是由: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处理后返回原始版本。
我从>nul
删除call :run
以允许kbd中断
语法`set“var =%var:= 0%”用零替换空格。
我将hr
的设置移到RetentionReport
生成之前,以防运行时间导致小时更改。
您的原始(可选)文件列表已写入.tmp
文件,该文件已被删除,因此我将其更改为.txt
文件。
for /d /r
执行递归目录名扫描,包括显示的表单中的当前目录。
如果pushd
失败(它在包含带有delayedexpansion
的!的目录名上),那么它是无效的,所以只有errorlevel
为0才应该{{ 1}}和call
被执行。
listfolder只检查一个目录 - 当前。
需要将日期转换为yyyymmdd表格以进行适当比较。这需要在提供的日期执行一次(在循环内永久地重新转换它们没有一点),这是在开始时完成的。
popd
的主要用途是访问变量的值,其中值在循环内变化;但是,delayedexpansion
测试(以及if defined
和if exist
)会对运行时值起作用,而不会对解析时值起作用。因此,这种方法避免使用if errorlevel n
- 用于演示,如果没有别的话。
最后的评论:执行具有适当日期的delayedexpansion
和空目录将列出在所选日期之后生成的文件。因此,可以将xcopy /L /D
运行到两个日期的空目录,以生成文件计数; #earlier - #later = #between。
XCOPY /L /D
中的文件数量(=行数)将等于MONTH_files.txt
(和GRANDTOTAL
) - 但最好删除MONTHTOTAL
at开始(不再是MONTH_files.txt
文件...)
答案 2 :(得分:0)
除递归堆栈限制外,还有其他问题。 IF语句不知道如何比较日期。它只知道字符串和数字。为了正确比较日期,您必须将日期重新格式化为YYYYMMDD格式。
应一次性收集输出文件名中使用的时间戳信息。您现有的代码获取流程开始时的小时,以及流程结束时的日期,分钟和秒。不好。开始和结束时间之间可能存在显着的时间差。
Batch有两种类型的递归错误:
1)在一个CALL级别内只有31个SETLOCAL级别。
2)允许可变数量的递归CALL,具体取决于Windows版本,机器内存,......
无法增加堆栈的大小。如果出现递归错误,则必须寻找减少递归量的方法。在你的情况下,你可以简单地让FOR / R为你做所有的递归!
我修改了脚本,以便在传递第4个参数时生成文件列表。我还将时间戳合并到文件列表文件名中。
代码假定您的机器的文件日期/时间值以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"