Thursday, December 06, 2012

Elapsed time in a Windows Shell script

That is easy to do in bash (just add time in front of the command you want to time), and it is also possible to do it in DOS. It just took me about 70 lines of script ;0) Here is an example:
 @setlocal
 @echo off
 set begintime=%time%
 @echo -----------------------------------------
 @echo Starting: %begintime% ...
 set /p var=Tell me something and hit [return] ^> 
 @echo -----------------------------------------
 set finishtime=%time%
 call :timediff %begintime% %finishtime%
 ::@echo.
 @echo Done at %finishtime%, it took you %timetaken% sec to tell me [%var%].
 @echo -----------------------------------------
 goto eos
 ::
 :timediff
 :: echo %1, %2
 set starttime=%1
 set endtime=%2
 if [%starttime:~1,1%] == [:] set starttime=0%starttime%
 if [%endtime:~1,1%] == [:] set endtime=0%endtime%
 set startcsec=%starttime:~9,2%
 set startsecs=%starttime:~6,2%
 set startmins=%starttime:~3,2%
 set starthour=%starttime:~0,2%
 set /a starttime=(%starthour%*60*60*100)+(%startmins%*60*100)+(%startsecs%*100)+(%startcsec%) 
 ::
 set endcsec=%endtime:~9,2%
 set endsecs=%endtime:~6,2%
 set endmins=%endtime:~3,2%
 set endhour=%endtime:~0,2%
 if %endhour% LSS %starthour% set /a endhour+=24
 set /a endtime=(%endhour%*60*60*100)+(%endmins%*60*100)+(%endsecs%*100)+(%endcsec%)
 set /a timetaken= ( %endtime% - %starttime% )
 set /a timetakens= %timetaken% / 100
 set timetaken=%timetakens%.%timetaken:~-2%
 goto eos
 :: End Of Script
 :eos
 @endlocal
Notice the code under the timediff label, this is where the skill is. It takes the time before and the time after as parameters, and computes the value of a timetaken variable that you can display.
It is all based on the value of the %time% variable:
 Prompt> echo %time%
 10:44:29.25
and also on the substring manipulation, performed with the ":~" characters.
It will generate an output like this one:
 -----------------------------------------
 Starting: 10:40:02.63 ...
 Tell me something and hit [return] > Whatever
 -----------------------------------------
 Done at 10:40:11.17, it took you 8.54 sec to tell me [Whatever].
 -----------------------------------------
There is a gotcha, though. A problem remains, when you write something like this:
 set /a num=(010 + 010)
Windows comes up with
 16
It is because it considers that 010 is in octal, as such, understood as 8 (decimal). Whatever begins with a '0' is an octal digit, so '08', '09', etc, are invalid numbers.
In the calculation above, you need to remove the leading '0' for the computation to be done in decimal...
Being aware of this, the script becomes (its timediff part, and on):
 :timediff
 :: echo from [%1] to [%2]
 set starttime=%1
 set endtime=%2
 if [%starttime:~1,1%] == [:] set starttime=0%starttime%
 if [%endtime:~1,1%] == [:] set endtime=0%endtime%
 set startcsec=%starttime:~9,2%
 set startsecs=%starttime:~6,2%
 set startmins=%starttime:~3,2%
 set starthour=%starttime:~0,2%
 :: Remove leading 0 (considered as octal numbers)
 call :removeLeadingZero %startcsec%
 set startcsec=%truncated%
 call :removeLeadingZero %startsecs%
 set startsecs=%truncated%
 call :removeLeadingZero %startmins%
 set startmins=%truncated%
 call :removeLeadingZero %starthour%
 set starthour=%truncated%
 ::
 set /a starttime=(%starthour%*60*60*100)+(%startmins%*60*100)+(%startsecs%*100)+(%startcsec%) 
 ::
 set endcsec=%endtime:~9,2%
 set endsecs=%endtime:~6,2%
 set endmins=%endtime:~3,2%
 set endhour=%endtime:~0,2%
 :: Remove leading 0 (considered as octal numbers)
 call :removeLeadingZero %endcsec% 
 set endcsec=%truncated%
 call :removeLeadingZero %endsecs%
 set endsecs=%truncated%
 call :removeLeadingZero %endmins%
 set endmins=%truncated%
 call :removeLeadingZero %endhour%
 set endhour=%truncated%
 ::
 if %endhour% LSS %starthour% set /a endhour+=24
 set /a endtime=(%endhour%*60*60*100)+(%endmins%*60*100)+(%endsecs%*100)+(%endcsec%)
 set /a timetaken= ( %endtime% - %starttime% )
 set /a timetakens= %timetaken% / 100
 set timetaken=%timetakens%.%timetaken:~-2%
 goto eos
 ::
 :removeLeadingZero
 set truncated=%1
 :top
 if not [%truncated:~1%] == [] (
   if [%truncated:~0,1%] == [0] (
     set truncated=%truncated:~1%
     goto top
   )
 )
 :: if not [%1] == [%truncated%] @echo [%1] becomes [%truncated%]
 goto eos
 :: End Of Script
 :eos
 @endlocal
Notice the new sub-routine removeLeadingZero. It leaves at least one character in the string to truncate ('0' is the same in octal and decimal). The statement if not [%truncated:~1%] == [] (... means "if the length of %truncated% is greater than 1". The statement
 if [%starttime:~1,1%] == [:] set starttime=0%starttime%
is here to manage the times before 10:00. It's returned like
  9:55:10.34
That makes the first ':' in position 2, instead of 3, as expected. This last line in transformed so it's saying
  09:55:10.34
This way, all is good!
Now, if you don't want to copy-paste this code every time you want to time a command, start the script this way:
 @setlocal
 @echo off
 set begintime=%time%
 @echo -----------------------------------------
 @echo Starting: %begintime% ...
 @echo -----------------------------------------
 :: Execution to time goes here
 %*
 ::
 @echo -----------------------------------------
 set finishtime=%time%
 call :timediff %begintime% %finishtime%
 ::@echo.
 @echo Done at %finishtime%, execution of [%*] took %timetaken% sec.
 @echo -----------------------------------------
 goto eos
 ::
 :timediff
 :: echo from [%1] to [%2]
   ...
Name it something like "elapsed.cmd", and run it this way:
 Prompt> elapsed java -version
 -----------------------------------------
 Starting:  9:24:08.48 ...
 -----------------------------------------
 java version "1.6.0_20"
 Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
 Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)
 -----------------------------------------
 Done at  9:24:08.63, execution of [java -version] took 0.15 sec.
 -----------------------------------------
And that's it. The full script is 74 lines long, including comments. Here is the full script:
@setlocal
@echo off
@echo Usage:
@echo   %0 ^<command ^<prm1 ^<prm2 ^<...^>^>^>
set begintime=%time%
@echo -----------------------------------------
@echo Starting: %begintime% ...
@echo -----------------------------------------
@echo %CD%^> %*
:: Execution to time goes here
%*
::
@echo -----------------------------------------
set finishtime=%time%
call :timediff %begintime% %finishtime%
::@echo.
@echo Done at %finishtime%, execution of [%*] took %timetaken% sec.
@echo -----------------------------------------
goto eos
::
:timediff
:: echo from [%1] to [%2]
set starttime=%1
set endtime=%2
if [%starttime:~1,1%] == [:] set starttime=0%starttime%
if [%endtime:~1,1%] == [:] set endtime=0%endtime%
set startcsec=%starttime:~9,2%
set startsecs=%starttime:~6,2%
set startmins=%starttime:~3,2%
set starthour=%starttime:~0,2%
:: Remove leading 0 (considered as octal numbers)
call :removeLeadingZero %startcsec%
set startcsec=%truncated%
call :removeLeadingZero %startsecs%
set startsecs=%truncated%
call :removeLeadingZero %startmins%
set startmins=%truncated%
call :removeLeadingZero %starthour%
set starthour=%truncated%
::
set /a starttime=(%starthour%*60*60*100)+(%startmins%*60*100)+(%startsecs%*100)+(%startcsec%)
::
set endcsec=%endtime:~9,2%
set endsecs=%endtime:~6,2%
set endmins=%endtime:~3,2%
set endhour=%endtime:~0,2%
:: Remove leading 0 (considered as octal numbers)
call :removeLeadingZero %endcsec%
set endcsec=%truncated%
call :removeLeadingZero %endsecs%
set endsecs=%truncated%
call :removeLeadingZero %endmins%
set endmins=%truncated%
call :removeLeadingZero %endhour%
set endhour=%truncated%
::
if %endhour% LSS %starthour% set /a endhour+=24
set /a endtime=(%endhour%*60*60*100)+(%endmins%*60*100)+(%endsecs%*100)+(%endcsec%)
set /a timetaken= ( %endtime% - %starttime% )
set /a timetakens= %timetaken% / 100
set timetaken=%timetakens%.%timetaken:~-2%
goto eos
::
:removeLeadingZero
set truncated=%1
:top
if not [%truncated:~1%] == [] (
  if [%truncated:~0,1%] == [0] (
    set truncated=%truncated:~1%
    goto top
  )
)
:: if not [%1] == [%truncated%] @echo [%1] becomes [%truncated%]
goto eos
:: End Of Script
:eos
@endlocal
    

No comments:

Post a Comment