Подтвердить что ты не робот

Как сравнить временные метки файлов в пакетном скрипте?

В пакетных файлах DOS метод достижения определенных целей несколько запутан. К счастью, есть фантастический справочный сайт для пакетного скриптинга: Simon Sheppard 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
)

Благодарю.

4b9b3361

Ответ 1

Вы можете найти более новый из двух файлов с одной строкой пакета script. Просто перечислите файлы в порядке дат, самые старые сначала, а это означает, что последним указанным файлом должен быть новый файл. Поэтому, если вы каждый раз сохраняете имя файла, последнее имя, помещенное в вашу переменную, будет самым новым.

Для примера:

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

Ответ 2

Dave Webb soution, в то время как отличный, конечно, будет работать только с файлами в том же каталоге.

Вот решение, которое будет работать над любыми двумя файлами.

Сначала получите время файла (см. Как получить последнюю измененную дату файла в командной строке Windows?).

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

Однако тогда необходимо вручную разбить дату и время на компоненты, так как Cmd.exe будет сравнивать их как жало, поэтому 2 > 10 и 10:00 AM > 2:00 PM.

Сравните сначала годы, затем месяцы, затем день, затем AM/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 - пробелы в пути к папке

Сравнительный код

: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

Ответ 3

серьезно, вы должны начать изучать что-то еще. Это не шутка. 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

Конечно, в новых версиях окон вы можете попробовать Powershell...

Ответ 4

Здесь более простое решение. Сопоставляя детали даты как одну строку от наиболее значимой до наименее значимой, мы можем просто провести простое сравнение строк по результату.

Другими словами, сравнение значений 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 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

Ответ 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 "Скопировано 0 файлов", поэтому условная команда не выполняется, а целевой файл никогда не перезаписывается. Если он новее, %% r "Скопирован 1 файл (ы)", поэтому ваш файл назначения вкратце является копией исходного файла, затем выполняется команда сборки, заменяя его новой версией того, на самом деле должно быть.

Я, наверное, должен был просто написать perl script.

(Примечание: вы также можете иметь xcopy обрабатывать ситуацию, когда целевой файл изначально не существует, помещая звездочку в конец имени файла назначения, если вы этого не сделаете, тогда xcopy isn "Не знаю, является ли назначение именем файла или папки, и нет значения по умолчанию для ответа на имя файла.)

Ответ 6

Я бы использовал xcopy для этого:

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

Поскольку в этом случае xcopy всегда возвращает true, вам необходимо отфильтровать его вывод с помощью команды findstr.

  • /L предназначен только для перечисления, ничего не копируется.
  • /D делает сравнение времени. Подсказка: поменяв местами "Файл1" и "Файл2", вы можете решить, как обрабатывать идентичные файлы.
  • Параметр/Y необходим только для того, чтобы избежать вопроса "Перезаписать существующий".

Это все и работает разными путями.

Ответ 7

Основываясь на Wes answer, я создал обновленный script. Обновления стали слишком многочисленными, чтобы вставлять комментарии.

  • Я добавил поддержку еще одного формата даты: формат, включая точки в датах. Добавлены инструкции о том, как включить больше форматов даты.
  • Ответ Wes на самом деле не работал, поскольку cmd не может сравнивать целые числа, превышающие 1 + 31 бит, поэтому я исправил это с преобразованием числовых строк в цитированные числовые строки.
  • script способен рассматривать отсутствующие файлы как самые старые файлы.
  • Добавлена ​​поддержка второй точности.

Моя версия ниже.

@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 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

Ответ 8

Бесплатная программа командной строки 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*