How to randomly select a line/ip out of a text file, and set it as a variable.
I mean something like:
set IP=TheLine/IPFromOfTheFile
Firstly you need to know the number of lines present in the text file textfile.txt
:
for /F %%N in ('type "textfile.txt" ^| find /C /V ""') do set NUM=%%N
Then calculate the random number in the range from 0
to NUM-1
:
set /A RND=%RANDOM%%%NUM
Finally pick the line from the text file and store it in LINE
. RND
holds the number of lines to skip by the for /F
loop:
if %RND% EQU 0 (set "SKIP=") else (set "SKIP=skip=%RND% ")
for /F "usebackq %SKIP%delims=" %%L in ("textfile.txt") do (
set "LINE=%%L"
goto :NEXT
)
:NEXT
This relies on the fact that the text file does not contain any empty lines.
The array based solutions seem like they ought to be fastest because they only read the file once. But variable manipulation becomes very slow when the environment becomes very large. So the array based solutions give non-linear performance. They can be painfully slow if the file is very large.
For a pure batch based solution that gives decent linear performance, regardless of file size, I like aschipfl's answer. However, it has problems with lines that begin with ;
due to the default for /f EOL value. It also fails if the file is empty. Here is a corrected version that is nearly perfect.
@echo off
setlocal
set "file=test.txt"
set "LINE="
for /F %%N in (
'type "%file%" ^| find /C /V ""'
) do set set /A RND=%RANDOM% %% %%N 2>nul || goto :BREAK
if %RND%>0 (set "skip=skip^=%RND%") else set "skip="
for /F usebackq^ %skip%^ delims^=^ eol^= %%L in ("%file%") do (
set "LINE=%%L"
goto :BREAK
)
:BREAK
set LINE
There is one remaining flaw with the above - If the selected line is empty, then it will return the next non-empty line instead due to the design of FOR /F.
The "standard" way to fix that is to use FINDSTR to prefix each line with the line number, and then use expansion find/replace to remove the prefix. Given the presence of the line number prefix, I like to use an additional FINDSTR to select the correct line, rather than relying on FOR /F
@echo off
setlocal
set "file=test.txt"
set "ln="
for /f %%N in (
'type "%file%" ^| find /c /v ""'
) do set /a rnd=%random% %% %%N + 1 2>nul || goto :result
for /f "delims=" %%L in (
'findstr /n "^" "%file%" ^| findstr "^%rnd%:"'
) do set "ln=%%L"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
:result
set ln
If you know that all your lines are <= 1021 bytes long, then there is a simpler solution for dealing with empty lines that reads the file with SET /P instead of FOR /F.
@echo off
setlocal
set "file=\utils\jrepl.bat"
set "ln="
for /f %%N in (
'type "%file%" ^| find /c /v ""'
) do set /a rnd=%random% %% %%N 2>nul || goto :result
<"%file%" (
for /l %%N in (1 1 %rnd%) do set /p "ln="
set "ln="
set /p "ln="
)
:result
set ln
Just for yucks, I decided to write a solution based on my JREPL.BAT regular expression text processing utility. An optimized hybrid JScript/batch script could be written, but JREPL is convenient if you already have it in your bag of tricks.
If all you need is to print a random line, then it is a simple one liner:
call jrepl "^.*" $0 /c /jmatch /jbeg "rnd=Math.floor(Math.random()*cnt)+1" /jbegln "skip=(rnd!=ln)" /f test.txt
A bit more code is needed to get the value in a variable:
@echo off
setlocal
set "ln="
for /f "delims=" %%L in (
'jrepl "^.*" $0 /c /n 1 /jmatch /jbeg "rnd=Math.floor(Math.random()*cnt)+1" /jbegln "skip=(rnd!=ln)" /f test.txt'
) do set "ln=%%L"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
:result
set ln
If the lines are all <= 1021 and you don't mind a temp file, then you could use SET /P
@echo off
setlocal
call jrepl "^.*" $0 /c /jmatch /jbeg "rnd=Math.floor(Math.random()*cnt)+1" /jbegln "skip=(rnd!=ln)" /f test.txt /o temp.txt
set "ln="
<temp.txt set /p "ln="
del temp.txt
:result
set ln
My solution is essentially identical to Aacini's but with a few differing details:
@echo off
setlocal enabledelayedexpansion
set i=0
for /f "tokens=*" %%x in (%1) do (
set line[!i!]=%%x
set /a i += 1
)
set /a j=%random% %% %i%
set ip=!line[%j%]!
The file name is an argument to this batch.
If I had gotten to this question earlier, I might've written Aacini's answer pretty much identically. He gets a +1 from me.
Anyway, how about something different for a little variety? Here's a batch one-liner that invokes a PowerShell helper.
for /f "delims=" %%I in ('powershell "get-random (gc \"%file%\")"') do set "IP=%%I"
It's slower than Aacini's solution, but it does have the advantage of simplicity. *shrug* If your IPs file includes any empty lines, you can add a selector to include only lines whose truthiness doesn't evaluate to false / empty.
for /f "delims=" %%I in ('powershell "gc \"%file%\" | ?{$_} | get-random"') do set "IP=%%I"
I'd also thought about getting a count of the lines in the text file using find /c /v "" txtfile
then using for /f "skip=%randomLineNumber%"
(like aschipfl's answer), but that'd be less efficient than the other answers already offered.
Try this:
@echo off
setlocal EnableDelayedExpansion
set "file=somefile.txt"
set "lines=0"
for /F "usebackq delims=" %%a in ("%file%") do set /a "lines+=1"
set /a "selected=%random%%%%lines%"
set "lines=0"
for /F "usebackq delims=" %%a in ("%file%") do (
if !lines! equ !selected! set "line=%%a"
set /a "lines+=1"
)
echo %line%
pause
@echo off
setlocal EnableDelayedExpansion
rem Load file lines into "line" array
set "num=0"
for /F "usebackq delims=" %%a in ("theFile.txt") do (
set /A "num+=1"
set "line[!num!]=%%a"
)
rem Select one random line
set /A "rnd=%random% %% num + 1"
set "IP=!line[%rnd%]!"
echo %IP%
For further details on array management in Batch files, see this post.