Randomly select a line out of a text file and set that line as a variable

前端 未结 6 768
挽巷
挽巷 2021-01-22 00:29

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
相关标签:
6条回答
  • 2021-01-22 00:53

    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.

    0 讨论(0)
  • 2021-01-22 00:55

    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
    
    0 讨论(0)
  • 2021-01-22 00:55

    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.

    0 讨论(0)
  • 2021-01-22 01:11

    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.

    0 讨论(0)
  • 2021-01-22 01:11

    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
    
    0 讨论(0)
  • 2021-01-22 01:12
    @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.

    0 讨论(0)
提交回复
热议问题