web-dev-qa-db-ja.com

バッチスクリプト内のファイルのタイムスタンプを比較するにはどうすればよいですか?

DOSバッチファイルでは、特定のことを達成する方法が多少難読化されています。幸いなことに、バッチスクリプトの素晴らしいリファレンスサイトがあります: Simon SheppardのSS64 。 (同じサイトには、Bashに関する多くの情報もあります。)

1つの問題は、ディレクトリが空かどうかに基づいて実行を分岐することです。明らかな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
)

ありがとう。

28
Rhubbarb

1行のバッチスクリプトで2つのファイルの新しい方を見つけることができます。ファイルを日付順にリストし、最も古いものを最初にリストします。つまり、リストされている最後のファイルが新しいファイルでなければなりません。そのため、毎回ファイル名を保存すると、変数に入力された姓が最新のファイルになります。

例えば:

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
33
Dave Webb

デイブウェッブの主張は素晴らしいものですが、もちろん同じディレクトリ内のファイルに対してのみ機能します。

これは、任意の2つのファイルで機能するソリューションです。

最初にファイル時間を取得します( 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
9
yoel halb

真剣に、あなたは何か他のものを学び始めるべきです。それは冗談ではありません。 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を試してみることができます...

8
ghostdog74

これが簡単な解決策です。日付部分を最上位から最下位まで単一の文字列として連結することにより、結果に対して単純な文字列比較を行うことができます。

つまり、YYYYMMDDAMPMHHMM値を比較すると、日付の各セグメントを個別に比較することなく、目的の結果が得られます。この値は、2番目の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

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は、Originファイルが新しい場合にのみ宛先ファイルを上書きします。新しくない場合、%% rは「0個のファイルがコピーされた」ため、条件付きコマンドは実行されず、宛先ファイルは上書きされません。新しい場合、%% rは「1ファイルのコピー」であるため、宛先ファイルはソースファイルのコピーであるため、ビルドコマンドが実行され、宛先ファイルの新しいバージョンに置き換えられます。実際にあるはずです。

たぶん、Perlスクリプトを書いたはずです。

(注:宛先ファイル名の最後にアスタリスクを付けることにより、xcopyが宛先ファイルが最初に存在しない状況を処理することもできます。そうしない場合は、xcopyは、宛先がファイル名であるかフォルダー名であるかがわからず、ファイル名への回答をデフォルトにするフラグがありません。)

6
Raven Black

Wes answer に基づいて、更新されたスクリプトを作成しました。更新が多すぎてコメントに入れることができませんでした。

  1. 日付にドットを含む形式のもう1つの日付形式のサポートを追加しました。より多くの日付形式を有効にする方法に関する指示を追加しました。
  2. Cmdは1 + 31ビットより大きい整数を比較できないため、Wesの回答は実際には機能しませんでした。そのため、数値文字列を引用符付き数値文字列に変換することで修正しました。
  3. スクリプトは、不足しているファイルを最も古いファイルと見なすことができます。
  4. 秒精度のサポートが追加されました。

私のバージョンは以下です。

@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