I'm new to batch scripting, so please be lenient with this question. When I'm subtracting 1 from 'TodayDay' variable, the value is not getting updated. Below is the line.
set /a "TodayDay=%TodayDay%-1"
My use case is to find if today's date is less than the 'lastOpenedDate' variable, I want to set the 'lastOpenedDate' to yesterday's
set lastOpenedDate=2017-12-22
IF %TodayYear%-%TodayMonth%-%TodayDay% LSS %lastOpenedDate% (
echo Before Subtraction TodayDay is %TodayDay%
set /a "TodayDay=%TodayDay%-1"
echo After Subtraction TodayDay is %TodayDay%
)
When I ran the above code, The output is:
Before Subtraction TodayDay is 20
After Subtraction TodayDay is 20
I got the other variable values from the below-mentioned code
for /F "skip=1 delims=" %%F in ('
wmic PATH Win32_LocalTime GET Day^,Month^,Year /FORMAT:TABLE
') do (
for /F "tokens=1-3" %%L in ("%%F") do (
set TodayDay=0%%L
set TodayMonth=0%%M
set TodayYear=%%N
)
)
set TodayDay=%TodayDay:~-2%
set TodayMonth=%TodayMonth:~-2%
What am I doing wrong?
You need to search SO using the top bar for delayed expansion. It's #1 FAQ.
change
set /a "TodayDay=%TodayDay%-1"
echo After Subtraction TodayDay is %TodayDay%
)
to
set /a "TodayDay=%TodayDay%-1"
)
echo After Subtraction TodayDay is %TodayDay%
which will make sense once you familiarise yourself with delayed expansion.
Now the next problem you'll run into (which won't show itself until the 8th or 9th of the month) is that in batch a leading 0 means "octal" so - you actually need
set /a "TodayDay=1%TodayDay%-1"
)
set "TodayDay=%TodayDay:~-2"
echo After Subtraction TodayDay is %TodayDay%
which adds 100 to the day number by stringing the 1 in front of the day number, then you need to get the last 2 characters.
Related
I need to create an integer array that stops at a limit number and then goes on after another fixed value. Something like [1 2 3 4 5 10 11 12 13 14 15 16], where 5 is the limit number and 10 where it restarts.
Can you do something like set arr = [1:%lim%, 10:%max%] ?
#ECHO OFF
SETLOCAL
CALL :buildlist mylist 1 5 10 16
ECHO list is %mylist%
PAUSE
GOTO :eof
:: Produce a list of integers
:: %1 is listname
:: %2 is start value
:: %3 is end-value for range
:: %4 is restart-value
:: %5 is end-value
:buildlist
SET "%1="
FOR /L %%v IN (%2,1,%5) DO (
IF %%v leq %3 CALL SET "%1=%%%1%% %%v"
IF %%v geq %4 CALL SET "%1=%%%1%% %%v"
)
CALL SET "%1=%%%1:~1%%"
GOTO :eof
Batch really doesn't have arrays, but can simulate one with a little imagination.
The above routine produces a list of values to the specification, ready for processing by a for statement.
Although it's possible to use delayedexpansion, it's possible to avoid that facility. By CALL ing a set command, the command is parsed before execution so to decode the hieroglyphics, where %1 is the variable name "var"
SET "%1=%%%1%% %%v"
is processed as:
SET "var=%var% %v"
as %% is an escaped-%, and %v will be replaced by the value of the metavariable (loop-control variable) %%v
Similarly,
SET "%1=%%%1:~1%%"
is executed as
SET "var=%var:~1%"
which deletes the first character, which will be a space.
This problem is about several for /L commands really, although it could be described in terms of nested if commands or in other different ways...
There are several different methods to give the values of the for commands, and also to implement they. I think this is the simplest one:
#echo off
setlocal EnableDelayedExpansion
set ranges="1:5" "10:16"
set "list="
for %%a in (%ranges::= 1 %) do for /L %%i in (%%~a) do set "list=!list! %%i"
echo list=%list%
Note that this method also works with several (more than two) ranges of values.
PS - This result is a list, not an array
Batch doesn't have OR operators, so you'll have to make do with two separate if statements - one for the first set of numbers and one for the second set of numbers. You can, however, put both of those in the same for loop:
#echo off
setlocal enabledelayedexpansion
set "lim=5"
set "max=10"
REM Setting counter to -1 because we're going to increment the counter and then
REM use it, and arrays famously start at zero.
set "counter=-1"
for /L %%A in (1,1,16) do (
if %%A LEQ %lim% (
set /a counter+=1
set "array[!counter!]=%%A"
)
if %%A GEQ %max% (
set /a counter+=1
set "array[!counter!]=%%A"
)
)
REM Display the contents of the array just to prove it worked.
REM Alphabetic order is used, so array[10] and array[11] are going to print before array[1]
set array[
I am trying to create some sort of handler for arguments passed into my script. The idea is to handle "pairs" of arguments together. So, that is to say 1 and 2, 3 and 4, 5 and 6, and so on... (NOT 2 and 3, 4 and 5, 6 and 7, and so on...)
I think batch is very cool, but I am very new to batch. I am lost as how to do it. So far I have managed to successfully put the arguments into an array with the following script:
SETLOCAL EnableDelayedExpansion
set /a count+=1
set "params[%count%]=%~1"
shift
if defined params[%count%] (
goto :repeat
) else (
set /a count-=1
)
Now I just need to handle each of the elements in pairs. I don't know how to do this at all. I know how to handle the elements individually with for /F "tokens=2 delims==" %%s in ('set params[') do, but I don't know how to do pairs
on the side can someone explain what the delayed expansion thing is supposed to do?
just for clarity, by "handle" I mean to manipulate it and pass it on as an argument onto another script. this must be done in pairs.
#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
:: remove variables starting $ or #
For %%b IN ($ #) DO FOR /F "delims==" %%a In ('set %%b 2^>Nul') DO SET "%%a="
SET /a count=0
SET "select=this is select's original value"
ECHO parameters received = %*
:loop
REM SET $... and #... to the parameter-pair.
SET $%count%=%1
SET #%count%=%2
IF NOT DEFINED $%count% GOTO process
IF NOT DEFINED #%count% ECHO "Incomplete pair"&GOTO :EOF
SET /a count +=1
:: shift twice to move 2 parameters
shift&shift&GOTO loop
:process
ECHO %count% pairs found
SET $
SET #
FOR /L %%a IN (1,1,%count%) DO (
REM note that this changes "select"
SET /a select=%%a - 1
CALL SET /a item1=%%$!select!%%
CALL SET /a item2=%%#!select!%%
ECHO pair %%a is !item1! and !item2! from $!select! and #!select! NOT $#%select%
)
ECHO ----------------- select = %select% ----------
SET /a count-=1
FOR /L %%a IN (0,1,%count%) DO (
REM note that this changes "select"
SET /a select=%%a + 1
ECHO pair !select! is !$%%a! and !#%%a! from $%%a and #%%a NOT $#%select%
GOTO :EOF
First, set up some initial values. The first for ensures that any variables starting $ or # are cleared.
Set $0 and #0 to the first and second parameters. If $0 is not set, then we have no further data, so process what we have. If $0 is set but #0 is not, we don't have a pair so complain and terminate.
If both #0 ans $0 are set, increment count, shift the parameters twice and run around the loop. The next pair will be placed in $/#1 and so on until the parameters are exhausted.
Now show the count of parameters and the $ and # values that have been set.
Next loop show unnecessary manipulation of the variables.
First set select to 1 less than the loop-counter.
then use a parsing trick to get the value of $[current value of select]. The line translates to "SET /a item1=%$n%" where n is the run-time value of select - ie. the value as it changes through the loop.
then show the results. item1 is changed within the block (parenthesised series of statements) so we need !item1 to access that changed value - %item1% would give us the value as it was when the loop was parsed. You can see that from the report for select on the same line.
Last loop is the same idea - but varying %%a from 0 to (count-1). Observe that select in the dashed echo line has the last value it was assigned in the previous loop.
So, once again we change select within the block, so we see a difference between !select! and %select%. We can also access $0 directly using !$%%a! - that is the current value of the variable [$ strung with the current value of the loop-control %%a]
#echo off
setlocal EnableDelayedExpansion
rem Store arguments one by one
set "count=0"
:repeat
set /A count+=1
set "param[%count%]=%~1"
shift
if "%~1" neq "" goto repeat
rem Show arguments in pairs
set /A countMIN1=count-1
for /L %%i in (1,2,%countMIN1%) do (
set /A j=%%i+1
for %%j in (!j!) do echo !param[%%i]! !param[%%j]!
)
For further details, see: Arrays, linked lists and other data structures in cmd.exe (batch) script
I would like to get the time stamp from the last two lines in a file that looks something like this using DOS:
2/3/2013 18:30:00 This is line 1
2/3/2013 19:24:05 This is line 2
2/3/2013 20:10:40 This is line 3
2/3/2013 21:06:00 This is line 4
2/3/2013 22:50:31 This is line 5
Currently, my script looks like this:
setlocal EnableDelayedExpansion
set i=0
for /f "tokens=2" %%x in (inputfile.txt) do (
set /a i=!i!+1
set time!i!=%%x
)
set /a lasttime=time!i!
set /a j=!i!-1
set /a prevtime=time!j!
echo %lasttime%
echo %prevtime%
endlocal
The output only have the hours portion, no minutes and seconds:
21
22
Please tell me how to make it work.
The issue is your using the SET /A command for assigning lasttime and prevtime.
SET /A is used for arithmetic operations, and everything you are feeding it must be converted to a number, even though you are not doing anything with the value apart from storing it to another variable. Your time!i! reference evaluates to 22:50:31 but in the context of the SET /A command it is immediately converted to the closest numeric value, which is 22. Same happens with time!j!.
To fix the issue, just use a simple SET command and mixed expansion, like this:
...
set lasttime=!time%i%!
set /a j=!i!-1
set prevtime=!time%j%!
...
Also, if all you need is the last two lines, you could consider an alternative approach. Instead of using a separate variable for every line of the text, you could use just two: one to store the current line, the other to store the previous value of the former. Here's what I mean:
setlocal EnableDelayedExpansion
for /f "tokens=2" %%x in (inputfile.txt) do (
set prevtime=!lasttime!
set lasttime=%%x
)
echo %lasttime%
echo %prevtime%
endlocal
I currently have the following code in a batch file Backup.bat on my desktop. it is used to back up an excel spreadsheet file each day and rename it by appending the current date and time. File.xlsx is copied and pasted to a new folder as File Sun-06-24-2012 23.21.46PM.xlsx
Currently the date and time is appended as Sun-06-24-2012 23.21.46PM.xlsx but i would like to have it append as Sun-06-24-2012 11.21.46PM.xlsx using the 12 hour clock rather than 24 hour clock format.
Below is the code i am currently using in Windows XP Professional. Would anyone know how to have the time appended in 12 hour clock format rather than 24 hour clock format as it is currently in the code below.
#For /F "tokens=1,2,3,4 delims=/ " %%A in ('Date /t') do #(
Set DayW=%%A
Set Day=%%B
Set Month=%%C
Set Year=%%D
Set All=%%A-%%B-%%C-%%D
)
#For /F "tokens=1,2,3 delims=:,. " %%A in ('echo %time%') do #(
Set Hour=%%A
Set Min=%%B
Set Sec=%%C
Set Allm=%%A.%%B.%%C
)
#For /F "tokens=3 delims=: " %%A in ('time /t ') do #(
Set AMPM=%%A
)
copy "C:\Temp\File.xlsx" "C:\Temp\DailyBackup\File %All% %Allm%%AMPM%.xlsx"
I agree with Joey's comment, I think you are better off with 24 hour format.
Also, your method for getting the date and time will break as soon as the code is transferred to another machine that uses a different date and/or time configuration.
But, here goes anyway...
You should get the entire time string from a single %TIME% expansion. Otherwise you run the risk of getting the hour:min:sec before midnight and the AMPM after midnight.
Put #ECHO OFF at the top, then you don't need to sprinkle # throughout your code.
#echo off
For /F "tokens=1,2,3,4 delims=/ " %%A in ('Date /t') do (
Set DayW=%%A
Set Day=%%B
Set Month=%%C
Set Year=%%D
Set All=%%A-%%B-%%C-%%D
)
For /F "tokens=1,2,3 delims=:,. " %%A in ('echo %time%') do (
set /a "Hour=100%%A%%100"
set Min=%%B
set Sec=%%C
)
if %Hour% geq 12 (
set AMPM=PM
set /a "Hour-=12"
) else set "AMPM=AM"
if %Hour% equ 0 set "Hour=12"
if %Hour% lss 10 set "Hour=0%Hour%"
set "Allm=%Hour%.%Min%.%Sec%%AMPM%"
echo on
copy "C:\Temp\File.xlsx" "C:\Temp\DailyBackup\File %All% %Allm%.xlsx"
Time /t is giving you the time in 24-hour format, presumably because that's the default for your computer's locale.
As time /t doesn't appear to offer any formatting options, probably the easiest thing to do is add an extra section to your batch file to convert the 24-hour clock to 12-hour.
Somewhere under Set Hour=%%A you just need to:
Subtract 12 from the hour if it's more than 12
Change '00' into '12'
For example:
If %Hour% gtr 12 (
Set /a Hour=%Hour%-12
)
If %Hour% == 00 (
Set Hour=12
)
The /a switch on Set tells it that the value to the right of the equals sign is a numerical expression to be evaluated.
This will leave you with no leading zero on the hour if it comes out from 1 to 9. You could get around this with another if statement to add a leading zero back in, or there might be a more elegant approach!
Let's say that I have a batch file that reads arbitrary integers from a file. The file is structured such that each line contains one integer, like so:
24
17
43
103
...
I need to calculate the average of the top 20 numbers that are in the file. In order to do that, I need some sort of data structure that stores the top 20 numbers. However, as far as I know there are no arrays in batch files. I may have to resort to using temporary files or some other method that I am not aware of. So my ultimate goal is to determine the best approach for implementing some sort of sorting algorithm for the batch file and calculate the average of the top 20 integers.
There is a constraint that I need to place on the problem. The file is pretty huge in terms of size (around ~500 lines) so I would rather not use temporary files due to the huge amount of read/write operations done (unless if you can convince me otherwise of course).
You can mimic arrays in batch. Take a look at Using Arrays in Batch Files.
The following solution uses arrays as described in the article by the link provided in the #Stoney's answer. It also uses zero-padding for correct sorting, which is a nice idea by #jeb, although this solution doesn't use the sort command. Instead, the sorting is automatically done by the SET command, whose output is used for iterating over the 'array'.
#ECHO OFF
SET top=5
SET cnt=0
FOR /F %%N IN (datafile) DO CALL :insert %%N
IF %cnt%==0 GOTO :EOF
IF %cnt% LSS %top% (SET threshold=0) ELSE SET /A threshold=cnt-top
SET s=0
SET i=0
FOR /F "tokens=2 delims=.=" %%A IN ('SET __number.') DO CALL :calc %%A
SET /A res=s/(cnt-threshold)-1000000
ECHO Average is %res%
PAUSE
GOTO :EOF
:insert
SET /A n=1000000+%1
SET /A __number.%n%+=1
SET /A cnt+=1
GOTO :EOF
:calc
SET /A i_prev=i
SET /A i+=__number.%1
IF %i% LEQ %threshold% GOTO :EOF
IF %i_prev% GEQ %threshold% (
SET /A s+=%1*__number.%1
) ELSE (
SET /A "s+=%1*(i_prev+__number.%1-threshold)"
)
Basically, the solution implements the following algorithm:
Pick numbers from the file one by one:
1.1. If the number is encountered for the first time, add it to the array with the count of 1.
1.2. If the number is a duplicate of an already added number, increase the corresponding count value by 1.
1.3. Increase the total count of numbers by 1.
Calculate the threshold value, which is the total count minus the top quantity of numbers whose average is to be calculated.
Iterate through the array items like this:
3.1. Increase the index by the current number's count value.
3.2. If the index exceeds the threshold:
If it has exceeded the threshold at the current iteration, increase the total sum by the product of the number and that part of its count that has exceeded the threshold.
If the threshold was exceeded earlier, increase the total sum by the product of the number and its count.
3.3. If the index doesn't exceed the threshold, omit the item.
Calculate the average as the total sum divided by the difference between total count and threshold.
You can use the sort command to sort the numbers, but there is the porblem, that sort uses a string sort and does not sort numbers, so a 2 seems to be greater than 10.
But this can be solved if you format all numbers to the same length into a temporary file.
So you get
024
017
043
103
...
Sort them with the /R (Reverse) option, to begin the output with the biggest number.
Then you can simply read 20 lines and build the sum for the average
for small files:
#Echo oFF
for /f %%a in (f1.txt) do Call :Append %%a
call :sort %sort%
pause
goto :EOF
:Append
call set sort=%%sort%% %*
goto :eof
:Sort
Setlocal EnableDelayedExpansion
Set/A n=1,s=0,c=s,r=s
for %%: In (%*) do (
Set /a c+=1
Set "nm.!c!=%%:")
:LP.1
if %s% EQU %c% Set/A n+=1,s=0
Set/A s+=1
Call :SPL %n% %s%
If %n% LEQ %c% goto :LP.1
:LP.2
Echo:!nm.%c%!
Set/A c-=1
If %c% GTR 0 goto :LP.2
Endlocal & goto :EOF
:SPL
If !nm.%1! GTR !nm.%2! (
Set "t=!nm.%2!"&Set "nm.%2=!nm.%1!"
Set "nm.%1=!t!"
)
goto :EOF
or a variant:
#echo off
for /f %%# in (f1.txt) do (
set x=##########%%#
call set #%%x:~-0xa%%==)
for /f "delims=#=" %%a in ('set ##') do echo(%%a
pause
there are also: gnu sort