如何比较批处理脚本中文件的时间戳?

时间:2009-11-06 11:28:23

标签: batch-file cmd filesystems

在DOS批处理文件中,实现某些事情的方法有些混淆。幸运的是,有一个很棒的批处理脚本参考站点:Simon Sheppard's SS64。 (同一网站也有大量关于 Bash 的信息。)

一个难点是根据目录是否为空来分支执行。 显而易见的if exist "%dir%\*.*"不起作用。但是可以使用这个条件执行技巧来完成:

( dir /b /a "%dir%" | findstr . ) > nul && (
  echo %dir% non-empty
) || (
  echo %dir% empty
)

另一个尴尬的问题是根据文件内容进行分支。 可以这样做:

( fc /B "%file1%" "%file2%" | find "FC: no differences encountered" ) > nul && (
  echo "%file1%" and "%file2%" are the same
) || (
  echo "%file1%" and "%file2%" are different
)

所以,我的问题是:
有没有办法根据文件的时间戳做分支?

这就是我想要的东西:

REM *** pseudo-code!
if "%file1%" is_newer_than "%file2%" (
  echo "%file1%" is newer
) else if "%file1%" is_older_than "%file2%" (
  echo "%file2%" is newer
) else (
  echo "%file1%" and "%file2%" are the same age
)

感谢。

8 个答案:

答案 0 :(得分:33)

您可以使用一行批处理脚本找到两个较新的文件。只需按日期顺序列出最早的文件,这意味着列出的最后一个文件必须是较新的文件。因此,如果每次都保存文件名,则变量中的最后一个名称将是最新文件。

例如:

SET FILE1=foo.txt
SET FILE2=bar.txt
FOR /F %%i IN ('DIR /B /O:D %FILE1% %FILE2%') DO SET NEWEST=%%i
ECHO %NEWEST% is (probably) newer.

遗憾的是,这并不能解决日期戳相同的问题。所以我们只需要先检查文件是否有相同的日期和时间戳:

SET FILE1=foo.txt
SET FILE2=bar.txt

FOR %%i IN (%FILE1%) DO SET DATE1=%%~ti
FOR %%i IN (%FILE2%) DO SET DATE2=%%~ti
IF "%DATE1%"=="%DATE2%" ECHO Files have same age && GOTO END

FOR /F %%i IN ('DIR /B /O:D %FILE1% %FILE2%') DO SET NEWEST=%%i
ECHO Newer file is %NEWEST%

:END

答案 1 :(得分:9)

Dave Webb的说法虽然很棒,但当然只能处理同一目录中的文件。

这是一个适用于任何两个文件的解决方案。

首先获取文件时间(参见How to get file's last modified date on Windows command line?)。

for %%a in (MyFile1.txt) do set File1Date=%%~ta
for %%a in (MyFile2.txt) do set File2Date=%%~ta 

然而,人们必须手动将日期和时间分成它的组件,因为Cmd.exe会将它们作为一个标记进行比较,因此2>上午10点和10点> 2:00 PM。

首先比较年份,然后是月份,然后是当天,然后是上午/下午,然后是小时,然后是分钟和秒,(实际上是耗费时间,但我没有一分钟更好的想法) ,最后看最后的代码。

但是,如果文件在同一分钟但在第二分钟之间不同,则此解决方案将无效。

如果你达到这种精确度,那么使用“forfiles”命令获取文件时间(参见https://superuser.com/questions/91287/windows-7-file-properties-date-modified-how-do-you-show-seconds)。

for /F "tokens=*" %%a in ('forfiles /m MyFile1.txt /c "cmd /c echo @fdate @ftime"') 
    do set File1Date=%%a
for /F "tokens=*" %%a in ('forfiles /m MyFile2.txt /c "cmd /c echo @fdate @ftime"') 
    do set File2Date=%%a    

请注意,“ForFiles”有一个限制,即它不能使用带空格的路径,因此如果您有一个带空格的路径,则必须先更改为该目录,请参阅forfiles - spaces in folder path

比较代码

:compareFileTime
set "originalFileTime=%1"
set "secondFileTime=%2"

for /F "tokens=1,2,3 delims= " %%a in (%originalFileTime%) do ( 
    set "originalDatePart=%%a"  
    set "originalTimePart=%%b"  
    set "originalAmPmPart=%%c"  
)
for /F "tokens=1,2,3 delims= " %%a in (%secondFileTime%) do (
    set "secondDatePart=%%a"
    set "secondTimePart=%%b"
    set "secondAmPmPart=%%c"
)
for /F "tokens=1,2,3 delims=/" %%a in ("%originalDatePart%") do (
        set "originalMonthPart=%%a"
        set "originalMonthDayPart=%%b"
        set "originalYearPart=%%c"

        rem We need to ensure that the year is in a 4 digit format and if not we add 2000 to it
        rem Cmd considers "50" > "100" but 50 < 100, so don't surround it with qoutes
    if %%c LSS 100 set "originalYearPart=20%%c
)
for /F "tokens=1,2,3 delims=/" %%a in ("%secondDatePart%") do (
    set "secondMonthPart=%%a"
    set "secondMonthDayPart=%%b"
    set "secondYearPart=%%c"    

    rem We need to ensure that the year is in a 4 digit format and if not we add 2000 to it
    rem Cmd considers "50" > "100" but 50 < 100, so don't surround it with quotes
        if %%c LSS 100 set "secondYearPart=20%%c    
)

if %originalYearPart% GTR %secondYearPart% goto newer
if %originalYearPart% LSS %secondYearPart% goto older

rem We reach here only if the year is identical
rem Cmd considers "2" > "10" but 2 < 10, so don't surround it with quotes or you will have to set the width explicitly
if %originalMonthPart% GTR %secondMonthPart% goto newer
if %originalMonthPart% LSS %secondMonthPart% goto older

if %originalMonthDayPart% GTR %secondMonthDayPart% goto newer
if %originalMonthDayPart% LSS %secondMonthDayPart% goto older

rem We reach here only if it is the same date
if %originalAmPmPart% GTR %secondAmPmPart% goto newer
if %originalAmPmPart% LSS %secondAmPmPart% goto older

rem we reach here only if i=t is the same date, and also the same AM/PM
for /F "tokens=1 delims=:" %%a in ("%originalTimePart%") do set "originalHourPart=%%a"

for /F "tokens=1 delims=:" %%a in ("%secondTimePart%") do set "secondHourPart=%%a"

rem Cmd considers "2" > "10" but 2 < 10, so don't surround it with qoutes or you will have to set the width explicitly
if %originalHourPart% GTR %secondHourPart% goto newer
if %originalHourPart% LSS %secondHourPart% goto older

rem The minutes and seconds can be compared directly
if %originalTimePart% GTR %secondTimePart% goto newer
if %originalTimePart% LSS %secondTimePart% goto older
if %originalTimePart% EQU %secondTimePart% goto same

goto older
exit /b

:newer
echo "newer"
exit /b

:older
echo "older"
exit /b

:same
echo "same"
exit /b

答案 2 :(得分:8)

认真地说,你应该开始学习别的东西。这可不是说笑。 DOS(cmd.exe)严重缺乏日期操作功能和更多缺陷。除了DOS批处理,vbscript

之外,这是本机提供的下一个更好的替代方案
Set objFS = CreateObject("Scripting.FileSystemObject")
Set objArgs = WScript.Arguments
strFile1 = objArgs(0)
strFile2 = objArgs(1)
Set objFile1 = objFS.GetFile(strFile1)
Set objFile2 = objFS.GetFile(strFile2)
If objFile1.DateLastModified < objFile2.DateLastModified Then
    WScript.Echo "File1: "&strFile1&" is older than "&strFile2
Else
    WScript.Echo "File1: "&strFile1&" is newer than "&strFile2
End If 

在命令行上运行

C:\test>dir
 Volume in drive C has no label.
 Volume Serial Number is 08AC-4F03

 Directory of C:\test

11/06/2009  07:40 PM    <DIR>          .
11/06/2009  07:40 PM    <DIR>          ..
11/06/2009  06:26 PM               135 file
11/02/2009  04:31 PM             4,516 m.txt

C:\test>cscript /nologo test.vbs file m.txt
File1: file is newer than m.txt

当然,在较新版本的Windows中,您可能想要试用Powershell ......

答案 3 :(得分:7)

这是一个更简单的解决方案。通过将日期部分连接为从最重要到最不重要的单个字符串,我们可以对结果进行简单的字符串比较。

换句话说,比较YYYYMMDDAMPMHHMM值将得到所需的结果,而无需单独比较日期的每个部分。此值是通过连接由第二个FOR命令提取的日期字符串的各个部分来获得的。

call :getfiledatestr path\file1.txt file1time
call :getfiledatestr path\file2.txt file2time
if %file1time% equ %file2time% (
  echo path\file1.txt is the same age as path\file2.txt to within a minute
) else if %file1time% lss %file2time% (
  echo path\file1.txt is older than path\file2.txt
) else (
  echo path\file1.txt is newer than path\file2.txt
)
goto :eof

@REM usage:
@REM :getfiledatestr file-path envvar
@REM result returned in %envvar%
:getfiledatestr
for %%f in (%1) do set getfiledatestr=%%~tf
@REM for MM/DD/YYYY HH:MM AMPM use call :appendpadded %2 %%c %%b %%a %%f %%d %%e
@REM for DD/MM/YYYY HH:MM AMPM use call :appendpadded %2 %%c %%b %%a %%f %%d %%e
@REM for YYYY/DD/MM HH:MM AMPM use call :appendpadded %2 %%a %%b %%c %%f %%d %%e
set %2=
for /f "tokens=1,2,3,4,5,6 delims=/: " %%a in ("%getfiledatestr%") do (
    call :appendpadded %2 %%c %%b %%a %%f %%d %%e
)
@goto :eof

@REM Takes an env var as the first parameter
@REM and values to be appended as the remaining parameters,
@REM right-padding all values with leading 0's to 4 places
:appendpadded
set temp_value=000%2
call :expand set %1=%%%1%%%%temp_value:~-4%%
shift /2
if "%2" neq "" goto appendpadded
set temp_value=
@goto :eof

@REM forces all variables to expand fully
:expand
%*
@goto :eof

答案 4 :(得分:5)

对于一个特定的情况,你想要以Makefile的方式做一些事情,只有当源文件更新时才基于源文件覆盖目标文件,我想出了这个可怕但简单的方法。如果您不关心比源文件旧的目标文件的现有内容,则这只是一个选项。

for /f "delims=" %%r in ('xcopy /D /Y /f /e "%inputdir%\%source_filename%" "%outputdir%\%dest_filename%"') do (
  IF "%%r" EQU "1 File(s) copied" %build_command% "%inputdir%\%source_filename%" "%outputdir%\%dest_filename%"
)

这样做,xcopy仅在原始文件较新时才覆盖目标文件。如果它不是更新的,则%% r是&#34; 0文件被复制&#34;,因此条件命令不会被执行,并且目标文件永远不会被覆盖。如果它更新,%% r是&#34; 1文件已复制&#34;,因此您的目标文件只是源文件的副本,然后执行构建命令,将其替换为新版本的无论目标文件实际应该是什么。

我应该写一个perl脚本。

(注意:您也可以xcopy处理目标文件最初不存在的情况,方法是在目标文件名的末尾加上星号;如果您不做然后xcopy不确定目的地是文件名还是文件夹名,并且没有标志来默认文件名的答案。)

答案 5 :(得分:2)

我会为此使用xcopy:

xcopy /L /D /Y PATH_TO_FILE1 PATH_TO_FILE2|findstr /B /C:"1 " && echo FILE1 is newer!

因为在这种情况下xcopy总是返回true,所以您需要使用findstr命令过滤其输出。

  • / L仅用于列出,不会复制任何内容。
  • / D正在进行时间比较。提示:通过交换File1和File2,您可以决定如何处理相同的文件。
  • / Y仅用于避免“覆盖现有”问题。

仅此而已,它可以使用不同的路径。

答案 6 :(得分:0)

基于Wes answer我创建了更新的脚本。更新太多,无法发表评论。

  1. 我添加了对另一种日期格式的支持:包含日期点的格式。添加了有关如何启用更多日期格式的说明。
  2. Wes答案实际上没有用,因为cmd无法比较大于1 + 31位的整数,所以我通过将数字字符串转换为引用的数字字符串来解决这个问题。
  3. 该脚本可以将丢失的文件视为最旧的文件。
  4. 添加了对第二精度的支持。
  5. 我的版本如下。

    @REM usage:
    @REM :getfiledatestr file-path envvar
    @REM result returned in %envvar%
    :getfiledatestr
    
    
    for %%A in (%1) do (
        set getfilefolderstr=%%~dpA
        set getfilenamestr=%%~nxA
    )
    
    @REM Removing trailing backslash
    if %getfilefolderstr:~-1%==\ set getfilefolderstr=%getfilefolderstr:~0,-1%
    
    @REM clear it for case that the forfiles command fails
    set getfiledatestr=
    
    for /f "delims=" %%i in ('"forfiles /p ""%getfilefolderstr%"" /m ""%getfilenamestr%"" /c "cmd /c echo @fdate @ftime" "') do set getfiledatestr=%%i
    @REM old code: for %%f in (%1) do set getfiledatestr=%%~tf
    
    set %2=
    
    @REM consider missing files as oldest files
    if "%getfiledatestr%" equ "" set %2=""
    
    @REM Currently supported date part delimiters are /:. and space. You may need to add more delimiters to the following line if your date format contains alternative delimiters too. If you do not do that then the parsed dates will be entirely arbitrarily structured and comparisons misbehave.
    for /f "tokens=1,2,3,4,5,6 delims=/:. " %%a in ("%getfiledatestr%") do (
    
    @REM for MM/DD/YYYY HH:MM:SS AMPM use call :appendpadded %2 %%c %%b %%a %%g %%d %%e %%f
    @REM for DD/MM/YYYY HH:MM:SS AMPM use call :appendpadded %2 %%c %%b %%a %%g %%d %%e %%f
    @REM for YYYY/DD/MM HH:MM:SS AMPM use call :appendpadded %2 %%a %%b %%c %%g %%d %%e %%f
        call :appendpadded %2 %%c %%b %%a %%g %%d %%e %%f
    )
    
    @goto :eof
    
    
    @REM Takes an env var as the first parameter
    @REM and values to be appended as the remaining parameters,
    @REM right-padding all values with leading 0's to 4 places
    :appendpadded
    set temp_value=000%2
    call :expand set %1=%%%1%%%%temp_value:~-4%%
    shift /2
    if "%2" neq "" goto appendpadded
    set temp_value=
    
    @REM cmd is not able to compare integers larger than 1+31 bits, so lets convert them to quoted numeric strings instead. The current implementation generates numeric strings much longer than 31 bit integers worth.
    call :expand set %1="%%%1%%%"
    
    @goto :eof
    
    
    @REM forces all variables to expand fully
    :expand
    %*
    @goto :eof
    

答案 7 :(得分:0)

免费的命令行程序WASFILE(https://www.horstmuc.de/wbat32.htm#wasfile)还将为您进行文件日期/时间比较。

这是网页上的快速程序编写:

*WasFile compares ..
.. time&date of two files (or directories),
.. the date of two files, time ignored
.. the date of a file with today-n (days)
.. time&date of a file with now-n (minutes)
Examples:
WasFile this.zip created before that.zip
WasFile this.zip modified after today-8
WasFile this.dat created before now-10
Using file stamps for created, modified (default) or accessed;
comparison: [not] before|after|sametime
Option to compare date only, ignore time: /DateLocal or /DateUTC*