web-dev-qa-db-fra.com

Comment comparer les horodatages des fichiers dans un script batch?

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

Une difficulté consiste à ramifier l'exécution selon qu'un répertoire est vide ou non. L'évident 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 la 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:
Existe-t-il un moyen de créer une branche en fonction de l'horodatage 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.

28
Rhubbarb

Vous pouvez trouver le plus récent de deux fichiers avec une ligne de script batch. Répertoriez simplement les fichiers par 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 du fichier à chaque fois, le dernier nom mis dans votre variable sera le fichier le plus récent.

Par 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.

Malheureusement, cela ne permet pas que les timbres à date soient identiques. Il nous suffit donc de vérifier d'abord si les fichiers ont 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
33
Dave Webb

Bien sûr, le support de Dave Webb ne fonctionnera que sur les fichiers du même répertoire.

Voici une solution qui fonctionnera sur deux fichiers quelconques.

Obtenez d'abord l'heure du fichier (voir Comment obtenir la dernière date de 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 ensuite décomposer manuellement la date et l'heure en ses composants, car Cmd.exe les comparera comme une piqûre, donc 2> 10 et 10h00> 14h00.

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

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

Si vous êtes à ce niveau de précision, obtenez la durée de vie 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 pour ce répertoire, voir forfiles - espaces 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
yoel halb

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 de nombreuses autres lacunes. Voici la meilleure alternative suivante 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 

l'exécuter en 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 voudrez peut-être essayer Powershell ...

8
ghostdog74

Voici une solution plus simple. En concaténant les parties de date en une seule chaîne de la plus significative à la moins significative, nous pouvons simplement faire une comparaison de chaîne simple 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 extraites par la deuxième 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
Wes

Pour une situation spécifique où vous voulez faire quelque chose dans le style d'un Makefile, écraser un fichier de destination basé sur un fichier source uniquement si le fichier source est plus récent, j'ai trouvé cette méthode hideuse mais simple. Ceci n'est qu'une option si vous ne vous souciez en aucune façon 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%"
)

Ce que cela signifie, xcopy n'écrase le fichier de destination que si le fichier d'origine est plus récent. S'il n'est pas plus récent, %% r est "0 fichier (s) copié", 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é", donc votre fichier de destination est brièvement une copie du fichier source, alors la commande de construction est exécutée, en la remplaçant par une nouvelle version de ce que le fichier de destination est en fait censé être.

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

(Remarque: vous pouvez également avoir xcopy gérer la situation où le fichier de destination 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 ne sait pas si la destination est un nom de fichier ou un nom de dossier et il n'y a pas d'indicateur par défaut pour la réponse au nom de fichier.)

6
Raven Black

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

  1. J'ai ajouté la prise en charge d'un autre format de date: un format comprenant des points dans les dates. Ajout d'instructions sur la façon d'activer davantage de formats de date.
  2. La réponse de Wes n'a en fait pas fonctionné car cmd n'est pas en mesure de comparer des entiers supérieurs à 1 + 31 bits, j'ai donc corrigé cela en convertissant les chaînes numériques en chaînes numériques entre guillemets.
  3. Le script peut considérer les fichiers manquants comme les fichiers les plus anciens.
  4. Ajout de la prise en charge de 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
Roland Pihlakas