简单地说,我怎样可以找出一个特定文件夹里的某个文件是否比X小时之前更旧?文件名和位置始终相同。
谢谢
简单地说,我怎样可以找出一个特定文件夹里的某个文件是否比X小时之前更旧?文件名和位置始终相同。
谢谢
在比较文件时间与批处理代码时,必须考虑以下因素:
有环境变量DATE和TIME,它们在访问时(而不是批处理执行开始时)以依赖于Windows语言和区域设置或Windows区域和语言设置的格式返回当前日期和时间。国家定义了日期和时间格式。
甚至可以在那里定义例如德国短日期格式为DD.MM.YYYY
(带前导零的日和月)和时间格式为HH:mm:ss
(带有前导零的24小时制的小时、分钟和秒)。但这并不意味着DATE和TIME的值真正使用此格式。例如,即使定义了HH:mm:ss
,当选择德国国家时,TIME的格式为H:mm:ss,ms
,这意味着小时没有前导零,但是分钟和秒钟有前导零,并且时间字符串中还有毫秒。日期字符串也可以根据区域设置有或没有星期几。
还有一个模式%~t
,可以获取目录或文件的最后修改日期和时间。在命令提示符窗口中运行call /?
和for /?
,并阅读这两个命令显示的所有帮助页面以获取详细信息。由%~t
返回的日期和时间字符串的格式以及在DIR命令输出上显示的格式也取决于Windows区域设置,但通常不等于变量DATE和TIME的格式。文件或目录的最后修改时间通常在日期/时间字符串中不返回秒值。
最好是在早上10点之前使用以下批处理文件找出当前用户在当前计算机上的格式。
@echo off
echo Current date/time: %DATE% %TIME%
for %%I in ("%~f0") do echo Batch file time: %%~tI
pause
在 Windows 文件系统中,有两种存储格式用于文件时间:
对于使用NTFS的存储介质,将使用NTFS精确时间。这是一个 64 位数字,表示自UTC时间1601年1月1日上午12:00以来已经过去的100纳秒间隔。
对于使用FAT、FAT16、FAT32或exFAT的存储介质,文件时间将使用DOS日期时间格式存储,其中包含两个16位值,分辨率为2秒,并使用当前本地时间。
这意味着在FAT媒体上不可能有奇数秒的文件,但在NTFS媒体上可以。将最后修改时间为奇数秒的文件从NTFS驱动器复制到FAT32驱动器上会导致具有相同文件但最后修改时间相差1秒的文件。
NTFS媒体上的文件时间以UTC存储。由%~t
或命令DIR返回的内容以及例如Windows资源管理器中显示的内容取决于:
更改这4个参数中的任何一个都会立即更改NTFS存储介质上所有显示的文件和目录的文件时间。
FAT媒体上的文件时间基本保持不变,因为使用创建、修改或最后访问时的本地时间,但请继续阅读了解详情。
由于 NTFS 媒体的文件时间存储为 UTC,但显示为本地时间,因此当前时区和该时区的夏令时对于文件时间比较非常重要,特别是对于 NTFS 和 FAT32 驱动器上的文件。
根据 Windows 版本和 FAT 媒体上的文件,时区和夏令时设置可能很重要。有关详情,请参阅下文。
有一个夏令时的自动调整设置,当启用时变得非常重要,因为默认情况下当前设置的时区定义了夏令时的调整。
例如,欧洲目前正在实行夏令时。在 Windows 的时钟设置中禁用此选项会导致 NTFS 媒体上显示的文件时间立即减少 1 小时,并且取决于 Windows 版本,许多 FAT 媒体上的文件也会发生变化。
由夏令时调整引起的这 1 小时时间差必须考虑在比较 NTFS 和 FAT 媒体上的文件时间时。
NTFS 受到所有 Windows NT 版本的支持。在支持 NTFS 的所有 Windows 版本上,文件和目录的时间行为相同。
但是,在 FAT 媒体上如何解释文件时间取决于 Windows 版本。在 Windows Vista 之前,FAT 媒体上的文件时间与时区更改、夏令时设置和自动调整夏令时无关。文件的最后修改日期/时间在 Windows 2000 和 Windows XP 上是恒定的。
但是,在 Windows Vista 及更高版本中,FAT 媒体上显示的文件时间取决于夏令时和自动调整夏令时。时区并不直接影响它。选择另一个当前使用的时区不会像对 NTFS 媒体上的文件那样更改 FAT 媒体上文件的显示时间。但是,如果当前时间处于活动时区的夏令时期间,并启用了自动调整夏令时,并且文件或目录的本地时间也处于夏令时期间,则 Windows Vista 及更高版本会添加 +1 小时。在标准时间内最后修改的 FAT 驱动器上的文件在全年内保持不变;只有在 DST 期间最后修改的文件根据当前启用自动 DST 调整的时间显示或不显示 +1 小时。
当在Windows 7机器上的FAT32驱动器上有一个文件夹的公共共享,并且使用Windows XP机器连接到此公共文件夹时,不同的FAT文件时间管理可能会非常令人困惑。例如,在2015-09-19 17:39:08上最后修改的文件存储在Windows 7 PC的FAT32驱动器上,今天由Windows 7以德国时区显示,文件时间为19.09.2015 17:39:08,但在未来2个月内即使没有修改也会变成19.09.2015 16:39:08,而Windows XP在连接的Windows 7 PC上显示相同的文件,今天和未来都是恒定的,文件时间为19.09.2015 17:39:08。
比较存储在档案(ZIP、RAR等)中的文件时间与NTFS或FAT媒体上的文件时间可能真的是一场噩梦。
对于这个任务,比较文件的最后修改时间和当前时间,只需要考虑日期和时间格式设置即可。
下面的批处理代码使用了我在批处理文件删除N天前的文件中详细解释的代码。在使用下面的代码之前,请先阅读该解释。
根据变量DATE和TIME的日期/时间格式以及本地Windows PC上文件返回的日期/时间字符串%~tI
,可能需要对代码进行小的修改。适当的提示已经在批处理代码的注释行中给出。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Get seconds since 1970-01-01 for current date and time.
rem From date string only the last 10 characters are passed to GetSeconds
rem which results in passing dd/mm/yyyy or dd.mm.yyyy in expected format
rem to this subroutine independent on date string of environment variable
rem DATE is with or without abbreviated weekday at beginning.
call :GetSeconds "%DATE:~-10% %TIME%"
rem Subtract seconds for 4 hours (4 * 3600 seconds) from seconds value.
set /A "CompareTime=Seconds-4*3600"
rem Define batch file itself as the file to compare time by default.
set "FileToCompareTime=%~f0"
rem If batch file is started with a parameter and the parameter
rem specifies an existing file (or directory), compare with last
rem modification date of this file.
if not "%~1" == "" if exist "%~1" set "FileToCompareTime=%~1"
rem Get seconds value for the specified file.
for %%F in ("%FileToCompareTime%") do call :GetSeconds "%%~tF:0"
rem Compare the two seconds values.
if %Seconds% LSS %CompareTime% (
echo File %FileToCompareTime% was last modified for more than 4 hours.
) else (
echo File %FileToCompareTime% was last modified within the last 4 hours.
)
endlocal
goto :EOF
rem No validation is made for best performance. So make sure that date
rem and hour in string is in a format supported by the code below like
rem MM/DD/YYYY hh:mm:ss or M/D/YYYY h:m:s for English US date/time.
:GetSeconds
rem If there is " AM" or " PM" in time string because of using 12 hour
rem time format, remove those 2 strings and in case of " PM" remember
rem that 12 hours must be added to the hour depending on hour value.
set "DateTime=%~1"
set "Add12Hours=0"
if not "%DateTime: AM=%" == "%DateTime%" (
set "DateTime=%DateTime: AM=%"
) else if not "%DateTime: PM=%" == "%DateTime%" (
set "DateTime=%DateTime: PM=%"
set "Add12Hours=1"
)
rem Get year, month, day, hour, minute and second from first parameter.
for /F "tokens=1-6 delims=,-./: " %%A in ("%DateTime%") do (
rem For English US date MM/DD/YYYY or M/D/YYYY
set "Day=%%B" & set "Month=%%A" & set "Year=%%C"
rem For German date DD.MM.YYYY or English UK date DD/MM/YYYY
rem set "Day=%%A" & set "Month=%%B" & set "Year=%%C"
set "Hour=%%D" & set "Minute=%%E" & set "Second=%%F"
)
rem Remove leading zeros from the date/time values or calculation could be wrong.
if "%Month:~0,1%" == "0" if not "%Month:~1%" == "" set "Month=%Month:~1%"
if "%Day:~0,1%" == "0" if not "%Day:~1%" == "" set "Day=%Day:~1%"
if "%Hour:~0,1%" == "0" if not "%Hour:~1%" == "" set "Hour=%Hour:~1%"
if "%Minute:~0,1%" == "0" if not "%Minute:~1%" == "" set "Minute=%Minute:~1%"
if "%Second:~0,1%" == "0" if not "%Second:~1%" == "" set "Second=%Second:~1%"
rem Add 12 hours for time range 01:00:00 PM to 11:59:59 PM,
rem but keep the hour as is for 12:00:00 PM to 12:59:59 PM.
if %Add12Hours% == 1 if %Hour% LSS 12 set /A Hour+=12
set "DateTime="
set "Add12Hours="
rem Must use two arrays as more than 31 tokens are not supported
rem by command line interpreter cmd.exe respectively command FOR.
set /A "Index1=Year-1979"
set /A "Index2=Index1-30"
if %Index1% LEQ 30 (
rem Get number of days to year for the years 1980 to 2009.
for /F "tokens=%Index1% delims= " %%Y in ("3652 4018 4383 4748 5113 5479 5844 6209 6574 6940 7305 7670 8035 8401 8766 9131 9496 9862 10227 10592 10957 11323 11688 12053 12418 12784 13149 13514 13879 14245") do set "Days=%%Y"
for /F "tokens=%Index1% delims= " %%L in ("Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N") do set "LeapYear=%%L"
) else (
rem Get number of days to year for the years 2010 to 2038.
for /F "tokens=%Index2% delims= " %%Y in ("14610 14975 15340 15706 16071 16436 16801 17167 17532 17897 18262 18628 18993 19358 19723 20089 20454 20819 21184 21550 21915 22280 22645 23011 23376 23741 24106 24472 24837") do set "Days=%%Y"
for /F "tokens=%Index2% delims= " %%L in ("N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N") do set "LeapYear=%%L"
)
rem Add the days to month in year.
if "%LeapYear%" == "N" (
for /F "tokens=%Month% delims= " %%M in ("0 31 59 90 120 151 181 212 243 273 304 334") do set /A "Days+=%%M"
) else (
for /F "tokens=%Month% delims= " %%M in ("0 31 60 91 121 152 182 213 244 274 305 335") do set /A "Days+=%%M"
)
rem Add the complete days in month of year.
set /A "Days+=Day-1"
rem Calculate the seconds which is easy now.
set /A "Seconds=Days*86400+Hour*3600+Minute*60+Second"
rem Exit this subroutine.
goto :EOF
rojo 提供了关于如何通过使用 wmic - Windows 管理工具命令行实用程序来忽略日期和时间格式设置、文件系统和 Windows 版本的信息。
使用 wmic 的优点是日期/时间字符串具有固定格式,因此批处理代码可以在所有 Windows 计算机上运行,无需对本地日期/时间格式进行调整。
另外,使用 wmic 时不需要考虑 Windows 版本,因为它内部使用 GetFileTime 函数(很可能如此)。
文件系统只在还需计算本地时间/文件时间与 UTC 的分钟偏移量时才需要考虑。在 FAT 驱动器上,wmic 命令返回 UTC 偏移量的 +***
,而在 NTFS 驱动器上,最后修改文件时间的分钟偏移量是正确的。
以下为同一文件在 NTFS 和 FAT32 驱动器上的示例:
NTFS: 20071011192622.000000+120
FAT32: 20071011192622.000000+***
+120是中欧时间(CET)加上活跃的夏令时(Daylight Saving Time)所组成的,也就是说中欧夏令时(CEST)为+120分钟。
以下批处理代码不会计算与UTC的分钟偏移量,因此在比较文件的最后修改时间和当前本地时间时不需要。
另外,在wmic命令行中必须始终指定文件名的完整路径。仅指定当前目录中的文件或带有相对路径的文件名是行不通的。并且文件名中的每个反斜杠都必须用一个额外的反斜杠进行转义。
使用wmic的主要缺点是速度较慢。根据Process Monitor日志记录(多次运行的平均值),以上批处理代码在我的Windows XP电脑上需要约50毫秒才能完成。而调用wmic两次的下面的批处理代码在Windows XP上完成同样的任务需要约1500毫秒(也是平均值)。在大量文件上使用wmic肯定不是好主意,至少在Windows XP上是这样,如果可以避免,因为本地日期/时间格式是已知的。
wmic
解决方案的另一个缺点是,在完全新安装的Windows 11版本(构建22572及更高版本)上,wmic.exe
不可用。请阅读Microsoft文档页面Windows客户端弃用功能,其中包含以下信息:
Windows管理规范命令行(WMIC)工具
WMIC工具在Windows 10版本21H1和Windows Server 21H1通用可用性渠道发布中已被弃用。此工具已被用于WMI的Windows PowerShell取代。注意:此弃用仅适用于命令行管理工具。WMI本身不受影响。
可以通过 设置 > 应用 > 可选功能 列表上的 WMIC 安装 wmic.exe
,以在 Windows 10/11 上安装缺失于目录 %SystemRoot%\System32\wbem
的 WMIC。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
if exist %SystemRoot%\System32\wbem\wmic.exe goto GetCurrent
echo ERROR: The WMI command-line utility wmic.exe is not installed.
echo(
echo There should not be used anymore batch files with WMIC on this computer.
echo(
echo There could be installed WMIC via Settings ^> Apps ^> Optional features.
echo(
pause
exit /B
:GetCurrent
rem Get seconds since 1970-01-01 for current date and time.
for /F "tokens=2 delims==." %%T in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do call :GetSeconds %%T
rem Subtract seconds for 4 hours (4 * 3600 seconds) from seconds value.
set /A "CompareTime=Seconds-4*3600"
rem Define batch file itself as the file to compare time by default.
set "FileToCompareTime=%~f0"
rem If batch file is started with a parameter and the parameter
rem specifies an existing file (or directory), compare with last
rem modification date of this file.
if not "%~1" == "" if exist "%~f1" set "FileToCompareTime=%~f1"
rem Get seconds value for the specified file.
set "DataFile=%FileToCompareTime:\=\\%"
for /F "usebackq tokens=2 delims==." %%T in (`%SystemRoot%\System32\wbem\wmic.exe DATAFILE where "name='%DataFile%'" GET LastModified /VALUE`) do call :GetSeconds %%T
rem Compare the two seconds values.
if %Seconds% LSS %CompareTime% (
echo File %FileToCompareTime% was last modified for more than 4 hours.
) else (
echo File %FileToCompareTime% was last modified within the last 4 hours.
)
endlocal
goto :EOF
rem Date/time format used by the command line utility of Windows
rem Management Instrumentation is always YYYYMMDDHHmmss with a
rem dot and a 6 digit microsecond value and plus/minus 3 digit
rem time offset to UTC in minutes independent on region and
rem language settings. Microsecond is always 000000 for a file
rem time. The minutes offset including time zone offset and
rem current daylight saving time offset is returned for a file
rem time only for a file on an NTFS drive. Minutes offset is
rem +*** for a file on a FAT drive (at least on Windows XP).
:GetSeconds
rem Get year, month, day, hour, minute and second from first parameter.
set "DateTime=%~1"
set "Year=%DateTime:~0,4%"
set "Month=%DateTime:~4,2%"
set "Day=%DateTime:~6,2%"
set "Hour=%DateTime:~8,2%"
set "Minute=%DateTime:~10,2%"
set "Second=%DateTime:~12,2%"
rem echo Date/time is: %Year%-%Month%-%Day% %Hour%:%Minute%:%Second%
rem Remove leading zeros from the date/time values or calculation could be wrong.
if %Month:~0,1% == 0 set "Month=%Month:~1%"
if %Day:~0,1% == 0 set "Day=%Day:~1%"
if %Hour:~0,1% == 0 set "Hour=%Hour:~1%"
if %Minute:~0,1% == 0 set "Minute=%Minute:~1%"
if %Second:~0,1% == 0 set "Second=%Second:~1%"
rem Must use two arrays as more than 31 tokens are not supported
rem by command line interpreter cmd.exe respectively command FOR.
set /A "Index1=Year-1979"
set /A "Index2=Index1-30"
if %Index1% LEQ 30 (
rem Get number of days to year for the years 1980 to 2009.
for /F "tokens=%Index1% delims= " %%Y in ("3652 4018 4383 4748 5113 5479 5844 6209 6574 6940 7305 7670 8035 8401 8766 9131 9496 9862 10227 10592 10957 11323 11688 12053 12418 12784 13149 13514 13879 14245") do set "Days=%%Y"
for /F "tokens=%Index1% delims= " %%L in ("Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N") do set "LeapYear=%%L"
) else (
rem Get number of days to year for the years 2010 to 2038.
for /F "tokens=%Index2% delims= " %%Y in ("14610 14975 15340 15706 16071 16436 16801 17167 17532 17897 18262 18628 18993 19358 19723 20089 20454 20819 21184 21550 21915 22280 22645 23011 23376 23741 24106 24472 24837") do set "Days=%%Y"
for /F "tokens=%Index2% delims= " %%L in ("N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N N Y N N") do set "LeapYear=%%L"
)
rem Add the days to month in year.
if "%LeapYear%" == "N" (
for /F "tokens=%Month% delims= " %%M in ("0 31 59 90 120 151 181 212 243 273 304 334") do set /A "Days+=%%M"
) else (
for /F "tokens=%Month% delims= " %%M in ("0 31 60 91 121 152 182 213 244 274 305 335") do set /A "Days+=%%M"
)
rem Add the complete days in month of year.
set /A "Days+=Day-1"
rem Calculate the seconds which is easy now.
set /A "Seconds=Days*86400+Hour*3600+Minute*60+Second"
rem Exit this subroutine.
goto :EOF
或者您可以创建一个简单的程序,例如使用C#:
// compile using c:\Windows\Microsoft.NET\Framework\v3.5\csc FileByAge.cs
using System;
using System.IO;
public static class Program
{
public static void Main(string[] args)
{
double age = double.Parse(args[0]);
string fileName;
while ((fileName = Console.ReadLine()) != null)
{
if(age >= (DateTime.Now - new FileInfo(fileName).CreationTime).TotalHours)
{
continue;
}
Console.WriteLine(fileName);
}
}
}
for /F "delims=" %f in ('dir /B *.txt^|FileByAge.exe 2') do echo %f
如@Paul所述,我们甚至可以构建一个巧妙的混合批处理/C#文件,使其自行编译和运行:
@echo off
setlocal
set CsFile=%TEMP%\%~n0.cs
set ExeFile=%TEMP%\%~n0.exe
pushd %SystemRoot%\Microsoft.NET\Framework
for /F %%f in ('dir /B /O:-N v*') do cd %%f & goto :BreakLoop;
:BreakLoop
(echo /* & type "%~f0") > "%CsFile%"
csc.exe /nologo /out:"%ExeFile%" "%CsFile%"
del "%CsFile%"
popd
more | "%ExeFile%" %*
del "%ExeFile%"
endlocal
goto:eof
*/
using System;
using System.IO;
public static class Program
{
public static void Main(string[] args)
{
var olderThen = args[0].Contains("older");
var targetAge = double.Parse(args[1]);
string fileName;
while ((fileName = Console.ReadLine()) != null)
{
if (string.Empty == fileName)
{
continue;
}
var fileAge = (DateTime.Now - new FileInfo(fileName).CreationTime).TotalHours;
if (olderThen ? targetAge > fileAge : targetAge < fileAge)
{
continue;
}
Console.WriteLine(fileName);
}
}
}
可以像这样使用:
for /F "delims=" %f in ('dir /B *.txt^|FileByAge.bat older 2') do echo %f
"delims="
即可解决。我还按照你的建议添加了一个自编译版本的脚本。 - Martinolder
的东西 - 例如 younger
。 - Martin纯批处理在日期计算上表现糟糕。如果你的脚本在午夜后不久运行会发生什么?如果时间检查跨越了夏令时变化呢?
我建议调用Windows脚本宿主并借鉴一种更好处理日期/时间数学的语言。这里有一个混合批处理/JScript脚本,可以很容易地计算文件或目录的年龄。将其保存为.bat扩展名并尝试一下。
@if (@CodeSection==@Batch) @then
@echo off
setlocal
set "file=C:\Path\To\File.ext"
for /f %%I in ('cscript /nologo /e:JScript "%~f0" "%file%"') do (
rem // value contained in %%I is in minutes.
if %%I gtr 240 (
echo %file% is over 4 hours old.
rem // do stuff here to take whatever actions you wish
rem // if the file is over 4 hours old.
)
)
goto :EOF
@end // end batch portion / begin JScript hybrid chimera
var fso = new ActiveXObject("scripting.filesystemobject"),
arg = WSH.Arguments(0),
file = fso.FileExists(arg) ? fso.GetFile(arg) : fso.GetFolder(arg);
// Use either DateCreated, DateLastAccessed, or DateLastModified.
// See http://msdn.microsoft.com/en-us/library/1ft05taf%28v=vs.84%29.aspx
// for more info.
var age = new Date() - file.DateLastModified;
WSH.Echo(Math.floor(age / 1000 / 60));
关于批处理/JScript混合脚本的更多信息,请参见这个GitHub页面。上面使用的样式类似于该页面上的Hybrid.bat。本答案基于另一个答案。值得一提的是,PowerShell也擅长处理日期计算;但WSH执行速度更快。
set "filename=myfile.txt"
rem extract current date and time
for /f "tokens=1-5 delims=.:, " %%a in ("%date% %time%") do (
set day=%%a&set mon=%%b&set yr=%%c&set hr=%%d&set min=%%e
)
rem extract file date and time
for /f "tokens=1-5 delims=.:, " %%a in ('"dir %filename%|find "%filename%""') do (
set fday=%%a&set fmon=%%b&set fyr=%%c&set fhr=%%d&set fmin=%%e
)
rem calculate age of file (in minutes)
set /a "age=((hr*60+min)-(fhr*60+fmin)+(24*60))%%(24*60)"
set /a "max=4*60"
if %age% geq %max% echo.file is older than 4 hours
'"dir %filename%|find "%filename%""'
而不是'dir "%filename%" ^| find "%filename%"'
。结果是每个文件都比4小时旧。即使进行了这个更正,批处理代码也只能用于不包含路径的文件名,因为__dir__输出的只是文件名,但__find__搜索带有路径的文件名。 - Mofi'dir "%filename%" ^| find "%filename%"'
从指定文件获取最后修改时间的下一个问题是,该文件必须没有路径,因此在当前目录中。 __find__搜索时没有参数/I
是区分大小写的。因此,如果第二行未精确拼写存储在存储介质上的文件名(不带路径),则__find__将始终无法在__dir__的输出中找到该文件的行。我还看到这段代码存在其他问题。抱歉! - Mofifilename
的文件名不能包含空格或这些字符之一:&()[]{}^=;!'+,`~
。
__2.__ 可以从代码中删除 set day=%%a&set mon=%%b&set yr=%%c&
和 set fday=%%a&set fmon=%%b&set fyr=%%c&
,因为只使用了小时和分钟环境变量来计算时间差异。
__3.__ 如果一个小时或一分钟是 08
或 09
,则代码不会正确工作,因为它们被视为无效的八进制数,并被处理为 0
。 - Mofi
wmic os get localdatetime
来获取当前日期和时间;用wmic datafile where "name='c:\\path\\to\\file.ext'" get lastmodified
来获取最后修改时间,格式相同。两个结果均为GMT时间,并在值的末尾报告时区/DST偏移量。 - rojowmic
方法还有另一个问题:您无法指定包含,
和)
的文件路径 -- 请参见我的问题:如何在 WMIC 的 WHERE 子句中同时转义逗号和右括号? - aschipfl