How do I get truly random numbers in batch? - windows

Everyone uses random numbers at one point or another. So, I need some truly random numbers (not pseudo-random*) generated from the command prompt, I also want a system that generates letters in a code line the size of: set RANDOM=%random% %%2 +1. And am I just trying to catch a non-existent butterfly? *pseudo-random is random that relies on outside info like time, batch's random generator is pseudo-random based on time, to test this open 2 new notepad .bat and name this file 1.bat
1.bat
start 2.bat
#echo off
cls
echo %random%
pause
2.bat
#echo off
cls
echo %random%
pause
What's happening?!? Well this is pseudo-random, as long as there is NO delay between the opening of the batch files, the batch file's numbers will be the same.

You're basically asking for entropy pool, and your platform is Windows, right?
Best bet is to use CryptGenRandom() function, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx. You could probably call it from Powershell
UPDATE
Apparently, there is a .NET wrapper for crypto, so you could use it from Powershell
[System.Security.Cryptography.RNGCryptoServiceProvider] $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
$rndnum = New-Object byte[] 4
# Generate random sequence of bytes
$rng.GetBytes($rndnum)
# rndnum is filled with random bits
....

On powershell it's pretty easy,
You can use the cmdlet Get-Random by itself or specify range like 1..100 | Get-Random
Another option is to call the System.Random object directly:
$Random = New-Object System.Random
$Random.Next()

Well, you may program a Batch file version of one of the tons of existent methods that generate pseudo-random numbers, or look for one that may be programmed in a Batch file with no further complications like The Minimal Standard Random Number Generator, or a simpler version of it like these Two Fast Implementations, and even modify one of they to made it simpler!
#echo off
setlocal EnableDelayedExpansion
rem Pseudo random number generator based on "The Minimal Standard"
rem http://www.firstpr.com.au/dsp/rand31/p87-carta.pdf
rem Antonio Perez Ayala aka Aacini
rem Initialize values
set /A a=16807, s=40
rem APA Modification: use current centiseconds for initial value
for /F "tokens=2 delims=." %%a in ("%time%") do if "%%a" neq "00" set /A s=1%%a %% 100
rem Example of use: generate and show 20 random numbers
for /L %%i in (1,1,20) do (
call :RandomGen
echo !rand!
)
goto :EOF
:RandomGen
rem Multiply the two factors
set /A s*=a
rem If the result overflow 31 bits
if %s% lss 0 (
rem Get result MOD (2^31-1)
set /A s+=0x80000000
)
rem APA modification: return just the low 15 bits of result (number between 0..32767)
set /A "rand=s & 0x7FFF"
exit /B
Of course, I know this method is not comparable with most of the "standard" methods, but I think it is enough for simple Batch file applications, like games...

The problem indeed is that %RANDOM% is only pseudo-random and depends when it's called; if two scripts use it (more-less) at the same time, it will have the same value.
Instead, call PowerShell from your batch script:
#for /f "tokens=*" %%i in ('powershell -Command "(1..100 | Get-Random)"') do #(
set "trueRandomInteger_between_1_and_100=%%i"
)

Related

Why can't you use a question mark in a batch for loop?

Preface
While writing a separate piece of code, I encountered a problem with question marks in for loops. As shown below, the question mark is not accessed in the for loop.
Batch file:
#echo off
for %%x in (the, quick, ?, brown, fox) do (
echo %%x
)
Output:
the
quick
brown
fox
This also does not work in the CMD (using %x instead of %%x), or when using "", [], ^, \, % or other common methods of character escaping.
Using a counter variable to determine the number of times the code within the parentheses was accessed only results in a total count of 4, meaning it is clearly not a problem with the echo command.
Question
Why doesn't a question mark work in a standard for loop, and how would I go about fixing it?
It's because ? will be expanded into a list of filenames one character long. The "naked" for is using that list as a list of filenames.
If you run the following commands, you'll see this in action:
c:\> echo xx >a
c:\> echo xx >b
c:\> for %i in (1, ?) do echo %x
1
a
b
If you look at Rob van der Woude's excellent scripting pages, you'll see that the for page has options for processing command output, numbers and files - it's not really suited for arbitrary strings.
One way to get around that is to provide your own for-like command as shown in the following example:
#echo off
setlocal enableextensions enabledelayedexpansion
rem Call the callback function for each argument.
set escapee=/
call :doFor :processEach 1 2 ? 4 5
echo.Escapee was %escapee%
rem Execute simple command for each argument.
call :doFor echo 6 7 ? 9 10
endlocal
goto :eof
:processEach
set escapee=%escapee%%1/
goto :eof
:doFor
setlocal
rem Get action.
set cbAction=%1
shift
:dfloop
rem Process each argument with callback or command.
if not "%1" == "" (
call %cbAction% %1
shift
goto :dfloop
)
endlocal&&set escapee=%escapee%
goto :eof
This provides a single functions which can handle both callbacks and simple commands. For more complex commands, provide a callback function and it will get called with each argument in turn. The callback function can be arbitrarily complex but keep in mind that, because it's operating within a setlocal, changes to environment variables cannot escape back to the caller.
As a way around this, it allows one variable, escapee, to escape the scope - you could also add more if needed.
For simple commands (like echo) where you just need the argument placed at the end, you do the same thing. It doesn't need a callback function but it's restricted to very simple scenarios.
Also keep in mind that, although this seems like a lot of code, the vast majority of it only needs to exist in one place. To use it, you simply need a one-liner like the sample:
call :doFor echo my hovercraft is full of eels
Also keep in mind that there may be other characters that do not fare well, even with this scheme. It solves the ? issue but others may still cause problems. I suspect that this would be an ideal opportunity to add PowerShell to your CV, for example, a command that's almost bash-like in it's elegance and zen-ness:
PShell> foreach ($item in #("1", "?", "3", "4")) { echo $item }
1
?
3
4
You could switch to FOR /F.
But FOR /F is used to process multiple lines to split them into tokens.
In your case you don't need multiple tokens, you need one loop per item.
That can be done by splitting the items with linefeeds.
I'm using # as item delimiter, but you are free to use any other character
#echo off
setlocal EnableDelayedExpansion
(set \n=^
%=EMPTY=%
)
set "itemList=the#quick#?#brown#fox"
for %%L in ("!\n!") DO (
FOR /F "delims=" %%x in ("!itemList:#=%%~L!") DO echo - %%x -
)
Output:
- the -
- quick -
- ? -
- brown -
- fox -
I've been coding with batch many years, and I'm suprised to realize this issue until now!
I found another way to deal with this problem. May be somebody prefers it, like me.
In my particularly case, I'm using the FOR LOOP to get some named arguments of the current function. This is what I did:
:SomeFunct
rem Replace ?
set "args=%*"
set "args=%args:?=`%"
rem Iterate args
for %%p in (%args%) do (
for /f "tokens=1,* delims=: " %%a in ("%%~p") do (
rem Get and store values
if /i "%%~a" equ "/a" set "argA=%%~b"
if /i "%%~a" equ "/b" set "argB=%%~b"
if /i "%%~a" equ "/c" set "argC=%%~b"
)
)
rem Restore ?
if defined argA set "argA=%argA:`=?%"
if defined argB set "argB=%argB:`=?%"
if defined argC set "argC=%argC:`=?%"
rem I use the args
rem ...
rem Return
goto:eof
I call the function like this:
rem Calling example
call:SomeFunct "/a:Is there" "/b:a question mark" "/c in the arguments?"

Command Prompt Search for files in a directory take the name of one random

Inside a directory c:\configs I have files with various extensions including the extension .rac. I need a script for the Windows command prompt that looks inside c:\configs for the names of the files that end with the extension .rac, ignoring other extensions. Then of all the names that end with .rac extension the script must choose a random one and process it with the command c:\programs\submit.exe namerandom.rac.
For example, suppose that random .rac file is called mosaic.rac, then the script executes the command c:\programs\submit.exe mosaic.rac. Of course the mosaic.rac name changes each time the script is runs because it is a random selected from the all the .rac files found.
Anyone have an idea in how to do and that can put example code?
#echo off
setlocal EnableDelayedExpansion & set n=0
for /f "delims=" %%a in ('dir /b /A-D "*.rac"') do (
set "f=%%a" & set "f[!n!]=!f!" & set /a "n+=1")
set /a c=%random% %% n
echo !f[%c%]!
Explanation:
Line #4: it make a pseudo array in f with n incremented by 1
Line #5: it take a random number between 0 and the total count of files called n with the help of: %random% modulo n
In this way, this creates a number of variables automatically according to their position then %random% %% n picks one.
You might as well picks some manually like this:
echo !f[0]! !f[1]! !f[2]! !f[3]! !f[4]! !f[5]! ...
To accomplish that, you may use the following...
Firstly, to get all .rac files, use the dir command. The /B switch specifies to output only a bare file list without any headers nor footers. If you want to search the given directory recursively, add the /S switch:
dir /B "C:\configs\*.rac"
Secondly, you need to count the number of returned .rac files. You can use a for /F loop (parsing the output of dir /B) together with set /A for that:
set /A COUNT=0
for /F "delims=| eol=|" %%L in (
'dir /B "C:\configs\*.rac"'
) do (
set /A COUNT+=1
)
Thirdly, you need to compute a random number in the applicable range. The built-in variable RANDOM retrieves a random number from 0 to 32767, so we need a little maths. The result will be a number from 0 to %COUNT% - 1:
set /A RNDNUM=%RANDOM%%%COUNT
Fourthly, you can use another for /F loop with the skip option to select a random file (we skip the previously calculated number RNDNUM of lines).
The if statement ensures that no skip option is provided in case the random number is 0.
The goto command ensures that only the selected file is passed to submit.exe. If you omitted it, every file after the selection would be passed over to submit.exe too, one after another:
if %RNDNUM% gtr 0 (
set SKIP=skip=%RNDNUM%
) else (
set SKIP=
)
for /F "%SKIP% delims=| eol=|" %%L in (
'dir /B "C:\configs\*.rac"'
) do (
start "" /WAIT "submit.exe" "%%~L"
goto :CONTINUE
)
:CONTINUE
Put together those parts to get the final script.
Type each command and append /? in command prompt to get the respective help text displayed.

Batch file setting variable in if/else statement in For Loop

I'm trying to create a batch file that generates a custom AVIsynth script per each file. Right now the batch file is set to execute from within the folder where the video files exist. What I need to do is get the creation time of the file to generate a timecode burn in. I have no problem getting the info I need. However, if the file was created in the afternoon I need it to be in 24hr time. For example, 2pm needs to display as 14.
I have a working if statement that creates a newth variable that adds 12 if need be. However, if it doesn't need it the variable persists. On each subsequent iteration of the loop the variable doesn't change.
My example. I have two files the first was created at 2pm the other at 12pm. The 2pm file is read first and the newth variable becomes 14. So far so good. On the next file the newth variable should become 12 but instead remains 14. How do I fix this?
#Echo Off & CLS
SetLocal EnableDelayedExpansion
For /F %%a In ('dir *.mpg /b') Do (
ECHO Processing "%%a"
echo %%~ta
set time=%%~ta
set th=!time:~11,2!
set tm=!time:~14,2!
set era=!time:~17,2!
echo !era!
if "!era!"=="PM" (
if !th! LSS 12 ( set /a newth=!th!+12 )
) else ( set /a newth=!th!)
echo !newth!
echo //AviSynth Test Script >scripts/%%a.avs
echo DirectshowSource^("%%~fa"^)>>scripts/%%a.avs
echo LanczosResize^(720,400^) >>scripts/%%a.avs
echo ShowSMPTE^(^) >>scripts/%%a.avs
ECHO Back to Console
Pause
)
It's a little messy because I've been using echo for debugging. But hopefully the problem is clear.
Here is a method with Wmic - Wmic is in XP pro and above.
#Echo Off & CLS
SetLocal EnableDelayedExpansion
For /F "delims=" %%a In ('dir *.mpg /b') Do (
ECHO Processing "%%a"
set "file=%cd%\%%a"
set "file=!file:\=\\!"
WMIC DATAFILE WHERE name="!file!" get creationdate|find ".">file.tmp
for /f %%a in (file.tmp) do set dt=%%a
set tm=!dt:~8,2!:!dt:~10,2!:!dt:~12,2!
del file.tmp
echo !tm!
echo //AviSynth Test Script >scripts/%%a.avs
echo DirectshowSource^("%%~fa"^)>>scripts/%%a.avs
echo LanczosResize^(720,400^) >>scripts/%%a.avs
echo ShowSMPTE^(^) >>scripts/%%a.avs
ECHO Back to Console
Pause
)
There are a few problems with your code. The major one is this sequence
if "!era!"=="PM" (
if !th! LSS 12 ( set /a newth=!th!+12 )
) else ( set /a newth=!th!)
With your first filetime "02:xx PM"
th=02, era=PM, so set /a newth=02+12 sets newth=14
With your second filetime "12:xx PM"
th=12, era=PM, so - do nothing, since there's no else action for !th! LSS 12
Hence, newth remains at 14.
So - what's the fix? Since you don't use newth further, we can't say for certain, but it appears you want 24-hour format - 4 digit hhmm.
DANGER, Will Robinson moment number 1:
You are dealing with numbers starring LEADING ZEROES. All well and good except where the value is 08 or 09, which batch bizarrely interprets as OCTAL since it begins 0.
DANGER, Will Robinson moment number 2:
set /a will suppress leading zeroes, so set /a newth=!th! will set newth to 7 for time 07:36 AM - not 07...
So - how to overcome all this?
IF !th!==12 SET th=00
SET th=!th: =0!
if "!era!"=="PM" (set /a newth=1!th!+12
SET newth=!newth:~-2!
) else ( set newth=!th!)
This forces 12 AM to 00 AM and 12 PM to 00 PM
Then replace any spaces with 0 (in case you have leading spaces, not zeroes)
Then, if era is PM, add 100 by stringing 1 before the 2-digit hour number, add 12 and grab the last 2 characters
Otherwise, just use the number in th
Unfortunately, made a little more complicated since you haven't told us whether you use or don't use leading zeroes in your time format. Nevertheless, the incomplete original calculation method is at fault.
DANGER, Will Robinson moment number 3:
time is a MAGIC VARIABLE - and you know what happened to Mickey when he got involved in things better left alone.
If you set time in a batch, then %time% or !time! will return the value you set. If you don't set it, then the value returned will be the system time. Same goes for DATE and a number of similar values (see set /? from the prompt - there's a list at the end)
here's how you can get the time stamp with seconds:
C:\>powershell -command "& {(gwmi -query """select * from cim_datafile where name = 'C:\\file.txt' """).lastModified;}"
C:\>powershell -command "& {(gwmi -query """select * from cim_datafile where name = 'D:\\other.txt' """).creationdate;}"
I've tried with WMIC but still cannot get the time stamp.As you are using Win7 you should have powershell installed by default.

Get a random sentence from a selection each time in Batch

Is there a way of making it so instead of saying the same echo that you set every time, you can give a list of echos and it chooses a random one to say each time it reaches that echo command?
Yep. Here's a proof of concept.
#echo off
setlocal enabledelayedexpansion
set string[0]=This is the first random line.
set string[1]=This is the second random line.
set string[2]=This is the third random line.
set /a idx=%random% * 3 / 32768
echo !string[%idx%]!
Here's more info on generating random numbers in Windows batch scripting.
#echo OFF
SETLOCAL
SET message0=message zero
SET message1=message one
SET message2=message two
SET message3=message three
SET message4=message four
:: running 10 times
FOR /l %%i IN (1,1,10) DO CALL :showme
GOTO :eof
:showme
SET /a select=%RANDOM% %% 5
CALL SET message=%%message%select%%%
ECHO %message%
GOTO :eof

Batch: how do you bulk rename files (getting close)?

I've seen people do this in Perl, but I'm wondering if there's a way to do it via batch? It's built into Windows, so I think it would be more useful to know how to do this with a batch script. It doesn't require installing anything onto a computer.
An example input name: myFile_55
An example output pattern: change myFile to picture and reduce the number by 13.
An example output: picture_42.
How would you approach this? I know a batch command to rename:
ren myFile_55 picture_42.
So, if I have a file named renamer.bat, I can add the following:
for /r %%x in (%1) do ren "%%x" %2.
Then I can type this command:
renamer.bat myfile* picture*.
I don't know how to reduce the numbers, though.
You can probably put the original file name through a for loop, and extract the name and numbers, do the math on the numbers, then plug it back in with the new name and number. As long as the filename format is name_number you can use this:
REM Allow for numbers to be iterated within the for-loop i.e. the i - z
SETLOCAL ENABLEDELAYEDEXPANSION
SET i=0
SET z=13
SET newName=picture
SET workDir=C:\Path\To\Files
REM Given that filenames are in the format of 'Name_number', we're going to extract 'number
REM and set it to the i variables, then subtract it by 13, then rename the original file
REM to what the newName_x which if the filename was oldName_23 it would now be newName_10
FOR /r %%X in (%1) do (
FOR /F "tokens=1-2 delims=_" %%A IN ("%%X") DO (
SET i=%%B
SET /A x=!i! - %z%
REM ~dpX refers to the drive and path of the file
REN "%%~dpX\%%A_%%B" "%newName%_!x!"
)
)
EDIT: Edited the REN command to include the drive and path of the original file. Changed from lower case x to upper case X as to not confuse %%~dpX.

Resources