P.S. For an example why the above isn't just an academical curiosity... Suppose you had a "wrapper" batch file to run an arbitrary command and measure the time it takes to complete. On the face of it, should be trivial - capture the %time% before and after, subtract, done. Yet, depending on how the "wrapper" batch actually runs the command, what's executed may not be what was intended, due to multi-pass re-parsing and un-escaping. Worse, the translation may not be easy to describe or explain, short of going into the more obscure corners of batch line processing.
Below is a snippet which tackles the issue by attempting to:
- retrieve the command line literally, safe against poison characters;
- execute the given line as passed, without further re-parsing.
This gives a simpe rule for verification - what's executed is exactly what would be echo'd off the same line.
Code: Select all
:: ticktock.cmd - run command and measure execution time
:: brief output sent to stderr, with just the command line and registered times
:: full output sent to stdout, including header and footer outlines
::
:: execution times expected to be shorter than a day
:: if count is off by an hour then it might just be daylight-saving late night
::
:: command line being executed is the same as if echo'd instead of ticktock'd
:: i.e. it undergoes one pass of parsing before ticktock receives the arguments
::
:: ticktock {command} ..run and profile {command}, full output
:: ticktock {command} ^>nul ..suppress {command} stdout output
:: ticktock {command} ^>nul 2^>^&1 ..suppress all {command} output
:: ticktock {command} >nul ..suppress all stdout output
:: ticktock {command} 2^>nul >nul ..suppress all but ticktock's stderr output
:: ticktock for /l %n in (1,1,10) do @{command} ..run {command} 10 times
@echo off
if "%~1"=="" ( echo.
@rem dump :: comment lines at the top of the file
for /f "usebackq delims=" %%a in ("%~f0") do (
set "z=%%~a" & setlocal enableDelayedExpansion
if not "!z:~0,1!"==":" endlocal & goto :eof
echo !z! & endlocal
)
goto :eof
)
setlocal enableExtensions disableDelayedExpansion
set "remArgs=%temp%\%random%.%time::=.%.tmp"
set prompt=@
>"%remArgs%" (
setlocal disableExtensions
echo on
for %%a in (%%a) do rem { %* }
@echo off
endlocal
)
prompt
setlocal enableDelayedExpansion
<"%remArgs%" (
set /p "args="
set /p "args="
set "args=!args:~7,-3!"
)
del "%remArgs%"
echo _______________________________________________________________________________
echo.
1>&2 echo !args!
echo ...............................................................................
@rem calibrate empty run
set "T0=%time%"
cmd /c exit 0
set "T1=%time%"
call :hmsc2xc %T0% XC0 & call :hmsc2xc %T1% XC1
call :xc-xc %XC1% %XC0% XX
@rem profile actual command
set "T0=%time%"
cmd /c !args!
set "T1=%time%"
call :hmsc2xc %T0% XC0 & call :hmsc2xc %T1% XC1
call :xc-xc %XC1% %XC0% XC
call :xc-xc /geq %XC% %XX% XC & if errorlevel 0 (set "lss=") else (set "lss=-")
call :xc2hmsc /trim %XX% TX & call :xc2hmsc /trim %XC% T
echo -------------------------------------------------------------------------------
if not "%XC%"=="%T%" (
1>&2 echo %lss%%XC% sec ^( %T% = %T1% - %T0% - %TX% ^)
) else (
1>&2 echo %lss%%XC% sec ^( = %T1% - %T0% - %TX% ^)
)
echo ===============================================================================
endlocal & endlocal & goto :eof
::-----------------------------------------------------------------------------
:hmsc2xc
setlocal enableExtensions
@rem assume hh:mm:ss.cc format
for /f "tokens=1-4 delims=:., " %%A in ("%~1") do (
set "H=%%~A" & set "M=%%~B" & set "S=%%~C" & set "C=%%~D"
)
@rem drop leading (but not sole) '0' lest mistaken for octal
if "%H:~0,1%" == "0" if not "%H:~1,1%" == "" set "H=%H:~1,1%"
if "%M:~0,1%" == "0" if not "%M:~1,1%" == "" set "M=%M:~1,1%"
if "%S:~0,1%" == "0" if not "%S:~1,1%" == "" set "S=%S:~1,1%"
set /a "X = (H * 60 + M) * 60 + S"
endlocal & (set %~2=%X%.%C%)
exit /b 0
::-----------------------------------------------------------------------------
:xc2hmsc
setlocal enableExtensions
if "%~1"=="/trim" (set "trim=1" & shift) else (set "trim=")
for /f "tokens=1* delims=.," %%s in ("%~1") do (set "X=%%s") & (set "C=%%t")
set /a "H = X / 3600", "M = (X %% 3600) / 60", "S = X %% 60"
if defined trim (call :xc2hmsc.trim) else (call :xc2hmsc.pad)
endlocal & (set %~2=%T%%C%)
exit /b 0
:xc2hmsc.trim
if %H% gtr 0 (set "T=%H%:") else (set "T=")
if %M% gtr 9 (set "T=%T%%M%:") else if not "%T%"=="" (set "T=%T%0%M%:") else if %M% gtr 0 (set "T=%M%:")
if %S% gtr 9 (set "T=%T%%S%.") else if not "%T%"=="" (set "T=%T%0%S%.") else (set "T=%S%.")
goto :eof
:xc2hmsc.pad
set "T=%H%:"
if %M% gtr 9 (set "T=%T%%M%:") else (set "T=%T%0%M%:")
if %S% gtr 9 (set "T=%T%%S%.") else (set "T=%T%0%S%.")
goto :eof
::.............................................................................
:xc-xc
setlocal enableextensions
if "%~1"=="/geq" (set "geq=1" & shift) else (set "geq=")
for /f "tokens=1* delims=.," %%s in ("%~1") do (set "X1=%%s") & (set "C1=%%t")
for /f "tokens=1* delims=.," %%s in ("%~2") do (set "X0=%%s") & (set "C0=%%t")
if %X1%%C1% lss %X0%%C0% (
@rem if /geq return '0.00' with negative errorlevel
if defined geq endlocal & set "%~3=0.00" & exit /b -1
@rem wrap around midnight (86,400 = 24 * 60 * 60)
set /a "X1 += 86400"
)
set /a "X = X1 - X0"
@rem wrap around full seconds
if "%C0:~0,1%" == "0" if not "%C0:~1,1%" == "" set "C0=%C0:~1,1%"
if "%C1:~0,1%" == "0" if not "%C1:~1,1%" == "" set "C1=%C1:~1,1%"
if %C1% lss %C0% (set /a "C1 += 100") & (set /a "X -= 1")
set /a "C = C1 - C0"
if "%C:~1,1%" == "" set "C=0%C%"
endlocal & (set "%~3=%X%.%C%")
exit /b 0
::_____________________________________________________________________________
Liviu