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

Rhu*_*arb 28 filesystems cmd batch-file

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

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

( dir /b /a "%dir%" | findstr . ) > nul && (
  echo %dir% non-empty
) || (
  echo %dir% empty
)
Run Code Online (Sandbox Code Playgroud)

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

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

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

这是我想要的东西:

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
)
Run Code Online (Sandbox Code Playgroud)

谢谢.

Dav*_*ebb 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.
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

  • 我想补充一点,只有当两个文件位于同一个文件夹中时,此解决方案才有效. (12认同)
  • @ user15964`DIR%FILE1 %% FILE2%`执行搜索这两个文件的目录列表,因此应始终返回这两个文件.添加`/ O:D`将它们置于日期顺序中,`/ B`使输出"裸",因此它只是文件名而不是文件大小等. (2认同)

yoe*_*alb 9

Dave Webb的一个很好的选择当然只适用于同一目录中的文件.

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

首先获取文件时间(请参阅如何在Windows命令行上获取文件的上次修改日期?).

for %%a in (MyFile1.txt) do set File1Date=%%~ta
for %%a in (MyFile2.txt) do set File2Date=%%~ta 
Run Code Online (Sandbox Code Playgroud)

然而,人们必须手动将日期和时间分成它的组件,因为Cmd.exe会将它们比作一个刺,因此2> 10和10:00 AM> 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    
Run Code Online (Sandbox Code Playgroud)

请注意,"ForFiles"有一个限制,即它不能使用带空格的路径,因此如果您有一个带空格的路径,则必须先更改为该目录,请参阅forfiles - 文件夹路径中的空格

比较代码

: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
Run Code Online (Sandbox Code Playgroud)

  • 而不是重新发明日期时间比较,为什么不将一个文件复制到另一个文件夹并使用原始答案?更快,更清洁.请注意在复制时保留文件修改时间. (2认同)

gho*_*g74 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 
Run Code Online (Sandbox Code Playgroud)

在命令行上运行它

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
Run Code Online (Sandbox Code Playgroud)

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


Wes*_*Wes 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
Run Code Online (Sandbox Code Playgroud)


小智 7

我会为此使用 xcopy:

xcopy /L /D /Y PATH_TO_FILE1 PATH_TO_FILE2|findstr /B /C:"1 " && echo FILE1 is newer!
Run Code Online (Sandbox Code Playgroud)

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

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

这就是全部,它适用于不同的路径。


小智 6

对于一个特定的情况,你想要以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%"
)
Run Code Online (Sandbox Code Playgroud)

这样做,xcopy只有在原始文件较新时才覆盖目标文件.如果它不是更新,%% r是"0 File(s)copies",因此条件命令不会执行,并且目标文件永远不会被覆盖.如果它更新,%% r是"1个文件被复制",所以你的目标文件只是源文件的副本,然后执行构建命令,将其替换为目标文件的新版本.实际上应该是.

我应该写一个perl脚本.

(注意:您也可以xcopy通过在目标文件名的末尾添加星号来处理目标文件最初不存在的情况;如果不这样做,则xcopy不确定目标是否是文件名或文件夹名称,没有标志来默认文件名的答案.)

  • 很好的技巧,因为它是独立的区域设置.让我们更进一步:`xcopy/DYLR src"dst*"| findstr/BC:"0"> nul && @echo dst是newer`这不会覆盖`dst`,只是检查它是否存在.是的,[星号是必要的](http://stackoverflow.com/a/26034267/1348138) (6认同)
  • 感谢您的尾随星号技巧。这是我第一次看到提到它。我之前见过的唯一解决方案是将“echo f”通过管道传输到 xcopy 中,这很俗气。 (2认同)