Comment comparer les horodatages des fichiers dans un script batch DOS?

Dans les fichiers batch DOS, la méthode de réalisation de certaines choses est quelque peu obscurcie. Heureusement, il existe un site de référence fantastique pour les scripts DOS batch: Ss64 de Simon Sheppard . (Le même site contient également de nombreuses informations sur Bash .)

Une difficulté consiste à brancher l'exécution selon qu'un répertoire est vide. L'évidence if exist "%dir%*.*" ne fonctionne pas. Mais cela peut être fait avec cette astuce d'exécution conditionnelle:

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

Un autre problème gênant est ramification selon le contenu du fichier. Encore une fois, cela peut être fait comme ceci:

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

Donc, ma question est la suivante:
Y a-t-il un moyen de faire une branche en fonction des horodatages des fichiers?

C'est le genre de chose que je veux:

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
)

Merci.

25
demandé sur Dave Webb 2009-11-06 14:28:22

6 réponses

Vous pouvez trouver le plus récent de deux fichiers avec une ligne de script batch. Il suffit de lister les fichiers dans l'ordre de date, le plus ancien en premier, ce qui signifie que le dernier fichier répertorié doit être le fichier le plus récent. Donc, si vous enregistrez le nom de fichier à chaque fois, le nom de famille mis dans votre variable sera le fichier le plus récent.

Pour, exemple:

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.

Cela ne fait malheureusement pas face aux timbres de date étant les mêmes. Nous avons donc juste besoin de vérifier si les fichiers ont d'abord la même date et l'heure:

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
32
répondu Dave Webb 2009-11-06 12:08:45

La soution de Dave Webb alors qu'un grand ne fonctionnera bien sûr que sur les fichiers dans le même répertoire.

Voici une solution qui fonctionnera sur deux fichiers.

Obtenez D'abord l'heure du fichier (Voir Comment obtenir la date de dernière modification du fichier sur la ligne de commande Windows?).

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

Cependant, il faut alors casser manuellement la date et l'heure dans ses composants depuis Cmd.exe les comparera comme une piqûre ainsi 2 > 10 et 10: 00AM > 2: 00PM.

Comparez d'abord les années, puis le mois, puis le jour, puis AM / PM, puis l'heure, puis la minute et la seconde, (en fait, cela prend du temps, mais je n'ai pas une meilleure idée sur la minute), voir le code final à la fin.

Cependant cette solution ne fonctionnera pas si les fichiers sont dans la même minute mais différents par la seconde.

Si vous êtes à ce niveau de précision, obtenez le filetime en utilisant la commande " forfiles "(voir 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    

Notez que "ForFiles" a une limitation qu'il ne peut pas prendre un chemin avec des espaces, donc si vous avez un chemin avec des espaces, vous devrez d'abord changer dans ce répertoire, voir forfiles-spaces dans le chemin du dossier

Code De Comparaison

: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
9
répondu yoel halb 2017-05-23 12:25:34

Sérieusement, vous devriez commencer à apprendre autre chose. Ce n'est pas une blague. DOS (cmd.exe) manque sérieusement de capacités de manipulation de date et beaucoup plus de lacunes. Voici la prochaine meilleure alternative fournie nativement en plus du lot 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 

Exécutez-le sur la ligne de commande

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

Bien sûr, dans les nouvelles versions de windows, vous pouvez essayer Powershell...

8
répondu ghostdog74 2009-11-06 11:39:25

Voici une solution plus facile. En concaténant les parties de date comme une seule chaîne de la plus significative à la moins significative, nous pouvons simplement faire une simple comparaison de chaîne sur le résultat.

En d'autres termes, la comparaison des valeurs YYYYMMDDAMPMHHMM donnera le résultat souhaité sans avoir à comparer individuellement chaque segment de la date. Cette valeur est obtenue en concaténant les différentes parties de la chaîne de date extraite par la seconde commande 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
7
répondu Wes 2014-07-03 06:06:27

Pour une situation spécifique dans laquelle vous voulez faire quelque chose dans le style D'un Makefile, en écrasant un fichier de destination basé sur un fichier source seulement si le fichier source est plus récent, je suis venu avec cette méthode hideuse mais simple. Ce n'est une option que si vous ne vous souciez pas du contenu existant d'un fichier de destination plus ancien que le fichier source.

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%"
)

Qu' xcopy ne remplace le fichier de destination si le fichier d'origine est plus récente. Si ce n'est pas plus récent, % % r est " 0 fichier(s) copié (s)", donc la commande conditionnelle ne s'exécute pas, et le fichier de destination n'est jamais écrasé. S'il est plus récent, %%r est "1 fichier(s) copié (s)", donc votre fichier de destination est brièvement une copie du fichier source, puis la commande build est exécutée, en le remplaçant par une nouvelle version de ce que le fichier de destination est censé être.

J'aurais probablement dû écrire un script perl.

(Remarque: vous pouvez également xcopy gérer la situation où la destination le fichier n'existe pas initialement, en mettant un astérisque à la fin du nom de fichier de destination; si vous ne le faites pas, alors xcopy n'est pas sûr si la destination est un nom de fichier ou un nom de dossier et il n'y a pas d'indicateur pour la réponse par défaut au nom de fichier.)

5
répondu Raven Black 2015-04-17 13:25:13

Basé sur réponse Wes j'ai créé un script mis à jour. Les mises à jour sont devenues trop nombreuses pour être mises en commentaires.

  1. j'ai ajouté le support pour un autre format de date: un format comprenant des points dans les dates. Ajout d'instructions sur la façon d'activer plus de formats de date.
  2. la réponse Wes n'a pas fonctionné puisque cmd n'est pas capable de comparer des entiers supérieurs à 1+31 bits, donc j'ai corrigé cela en convertissant les chaînes numériques en chaînes numériques entre guillemets.
  3. le script est capable de considérer fichiers manquants en tant que fichiers les plus anciens.
  4. Ajout du support pour la deuxième précision.

MA version est ci-dessous.

@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
0
répondu Roland Pihlakas 2017-05-23 12:17:48