ตัวเลขสุ่มจากช่วงใน Bash Script


198

ฉันต้องสร้างหมายเลขพอร์ตสุ่มระหว่าง2000-65000จากเชลล์สคริปต์ ปัญหาคือ$RANDOMหมายเลข 15 บิตดังนั้นฉันติดอยู่!

PORT=$(($RANDOM%63000+2001)) จะทำงานได้ดีถ้ามันไม่ได้สำหรับขนาดที่ จำกัด

ไม่มีใครมีตัวอย่างของวิธีที่ฉันสามารถทำได้อาจโดยการแยกบางสิ่งบางอย่างจาก/dev/urandomและรับมันในช่วงหรือไม่

คำตอบ:


398
shuf -i 2000-65000 -n 1

สนุก!

แก้ไข : ช่วงนี้รวมอยู่ด้วย


7
ฉันคิดว่าshufค่อนข้างเร็ว ๆ นี้ - ฉันเคยเห็นมันในระบบ Ubuntu ในช่วงสองสามปีที่ผ่านมา แต่ไม่ใช่ RHEL / CentOS ปัจจุบัน
Cascabel

5
นอกจากนี้มันอาจจะใช้ได้สำหรับการใช้งานนี้ แต่ฉันเชื่อว่าshufจริง ๆ แล้วอนุญาตการป้อนข้อมูลทั้งหมด นี่เป็นตัวเลือกที่ไม่ดีหากคุณกำลังสร้างตัวเลขสุ่มบ่อยมาก
Cascabel

3
@Jefromi: ในระบบของฉันใช้การทดสอบนี้time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doneและเปรียบเทียบend=1กับend=65535พบว่ามีการปรับปรุง 25% สำหรับช่วงที่สั้นกว่าซึ่งมีความแตกต่างประมาณ 4 วินาทีในการทำซ้ำกว่าล้านครั้ง และมันก็เป็นจำนวนมากได้เร็วกว่าการดำเนินการคำนวณทุบตี OP ของล้านครั้ง
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

9
@Dennis Williamson: เรียกใช้การทดสอบของคุณด้วย-n 1ความแตกต่างของเวลาเล็กน้อยแม้ว่าจะend=4000000000เป็น เป็นการดีที่จะทราบว่าshufทำงานได้ดีไม่ยาก :-)
leedm777

6
ฉันไม่มี shuf บน mac ของฉัน :(
Viren

79

ใน Mac OS X และ FreeBSD คุณอาจใช้ jot:

jot -r 1  2000 65000

5
ในตัวอย่างนี้jotมีการแจกแจงที่ไม่เป็นธรรมสำหรับช่วงต่ำสุดและสูงสุดของช่วง (เช่น 2000 และ 65000) อีกนัยหนึ่ง min และ max จะถูกสร้างขึ้นไม่บ่อยนัก ดูjot คำตอบของฉันสำหรับรายละเอียดและวิธีแก้ปัญหา
Clint Pachl

jotยังมีอยู่ในการกระจายมากที่สุด GNU / Linux
ธ อร์

43

ตามหน้า man bash $RANDOMมีการกระจายระหว่าง 0 และ 32767; นั่นคือมันเป็นค่า 15 บิตที่ไม่ได้ลงชื่อ สมมติว่า$RANDOMมีการกระจายอย่างสม่ำเสมอคุณสามารถสร้างจำนวนเต็ม 30 บิตที่ไม่ได้ลงชื่อแบบสม่ำเสมอดังต่อไปนี้:

$(((RANDOM<<15)|RANDOM))

เนื่องจากช่วงของคุณไม่ใช่กำลัง 2 การทำงานแบบโมดูโลแบบง่าย ๆเกือบจะให้การกระจายที่เหมือนกัน แต่มีช่วงอินพุต 30 บิตและช่วงเอาต์พุตน้อยกว่า 16 บิตตามที่คุณมีในกรณีของคุณ สิ่งนี้ควรอยู่ใกล้พอจริง:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))

1
ตัวแปร$RANDOMไม่สามารถใช้ได้ในทุกเชลล์ กำลังมองหาวิธีอื่น
Lukas Liesis

ถ้าฉันเข้าใจสิ่งนี้อย่างถูกต้องคุณกำลังกระจาย 32,000 หมายเลขท่ามกลางช่วง 1,000,000,000 แต่พวกเขาจะกดจำนวนทวีคูณของ 2 ^ 15 เท่านั้นคุณกำลังนับข้าม 2 ^ 15 โดยไม่กรอกตัวเลขทั้งหมดระหว่าง 1 ถึง 2 ^ 30 เท่า ๆ กันซึ่งก็คือการกระจายตัวแบบสม่ำเสมอ
isomorphismes

@ isomorphismes โปรดทราบว่ารหัสอ้างอิง$RANDOMสองครั้ง บนเชลล์ที่สนับสนุน$RANDOMค่าใหม่จะถูกสร้างขึ้นทุกครั้งที่มีการอ้างอิง ดังนั้นรหัสนี้เติมบิต 0 ถึง 14 ด้วย$RANDOMค่าหนึ่งและเติมบิต 15 ถึง 29 ด้วยค่าอื่น สมมติว่า$RANDOMมีความเหมือนกันและเป็นอิสระซึ่งครอบคลุมค่าทั้งหมดตั้งแต่ 0 ถึง 2 ** 30-1 โดยไม่ข้ามอะไรเลย
Jesin

41

และนี่คือส่วนหนึ่งของ Python

randport=$(python -S -c "import random; print random.randrange(2000,63000)")

และอีกอันกับ awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'

6
อันนี้ได้รับ upvote จากฉัน ฉันเขียนสคริปต์ทุบตีสำหรับระบบต่าง ๆ และฉันเชื่อว่า awk อาจเป็นเครื่องมือที่มีมากที่สุดสำหรับงาน ทำงานบน mac os x และ centos โดยไม่มีปัญหาและฉันรู้ว่ามันจะทำงานบนเครื่องเดเบียนของฉันด้วยและอาจเป็นเครื่องปกติ ish * ระวังอื่น ๆ
John Hunt เมื่อ

6
อย่างไรก็ตามเมล็ดสุ่มของ awk ดูเหมือนจะรีเฟรชหนึ่งครั้ง / วินาทีดังนั้นคุณอาจต้องการ) หลีกเลี่ยงค่าใช้จ่ายทั้งหมดหรือ b) เริ่มต้นเมล็ดอีกครั้ง
John Hunt เมื่อ

+1 เนื่องจากสิ่งนี้ดูเหมือนว่าจะเป็น POSIX เดียวที่ไม่มีการรวบรวม: RANDOMไม่รับประกันโดย POSIX
Ciro Santilli 法轮功冠状病病六四事件法轮功

โดยใช้ผลการเลือกใน-S ImportError: No module named randomใช้งานได้ถ้าฉันลบมัน ไม่แน่ใจว่าเจตนาของ ghostdog นั้นคืออะไร
Chris Johnson

1
python -S -c "import random; print random.randrange(2000,63000)"ดูเหมือนว่าจะทำงานได้ดี อย่างไรก็ตามเมื่อฉันพยายามรับตัวเลขแบบสุ่มระหว่าง 1 ถึง 2 ฉันดูเหมือนจะได้รับ 1 เสมอ ... ความคิด?
Hubert LéVELé Gauvin

17

วิธีทั่วไปที่ง่ายที่สุดที่นึกได้คือ perl one-liner:

perl -e 'print int(rand(65000-2000)) + 2000'

คุณสามารถใช้ตัวเลขสองตัวได้เสมอ:

PORT=$(($RANDOM + ($RANDOM % 2) * 32768))

คุณยังต้องคลิปช่วงของคุณ มันไม่ใช่วิธีการสุ่มตัวเลข n-bit ทั่วไป แต่มันจะใช้ได้กับกรณีของคุณและทั้งหมดอยู่ในการทุบตี

ถ้าคุณต้องการที่จะน่ารักและอ่านจาก / dev / urandom คุณสามารถทำได้:

od -A n -N 2 -t u2 /dev/urandom

ที่จะอ่านสองไบต์และพิมพ์เป็น int ไม่ได้ลงนาม; คุณยังต้องทำการคลิปของคุณ


ฉันใช้เทคนิคนี้และสังเกตว่าในตอนนี้จากนั้นจะไม่มีการสร้างหมายเลขเพียงแค่ช่องว่างเปล่า
PdC

มันต้องมีการติดตั้ง Perl ผมเขียนสคริปต์ซึ่งควรจะทำงานในส่วนใหญ่ถ้าไม่ทุกเครื่อง Linux ที่ติดกับawkรุ่นจากคำตอบอื่น
Lukas Liesis

การเพิ่มตัวเลขสุ่มช่วยให้ได้ผลลัพธ์ปานกลางโดยมีค่าใช้จ่ายต่ำหรือสูง มันไม่ได้สุ่มอย่างสม่ำเสมอ
isomorphismes

@ isomorphismes ใช่ถ้าคุณแค่เพิ่มตัวเลขสุ่มสองตัว แต่สมมติว่าคุณอ้างถึงนิพจน์ที่สองที่นี่นั่นไม่ใช่สิ่งที่ทำ มันเป็นตัวเลขสุ่มใน [0,32767] บวกตัวเลือกสุ่มอิสระสำหรับบิตถัดไปเช่น 0 หรือ 32768 มันเหมือนกัน (มันไม่เหมาะสำหรับคำถามต้นฉบับ แต่เนื่องจากคุณต้องตัดช่วงด้วยการหมุนซ้ำ)
Cascabel

7

หากคุณไม่ใช่ผู้เชี่ยวชาญด้านทุบตีและกำลังมองหาสิ่งนี้ในตัวแปรในสคริปต์ทุบตีที่ใช้ Linux ลองสิ่งนี้:

VAR=$(shuf -i 200-700 -n 1)

ที่ทำให้คุณมีช่วง 200-700 $VARรวมอยู่ด้วย


5

นี่คืออีกหนึ่ง ฉันคิดว่ามันจะทำงานได้ทุกอย่าง แต่ตัวเลือกการเรียงลำดับแบบสุ่มไม่สามารถใช้ได้ในกล่องเซ็นโตสของฉันในที่ทำงาน

 seq 2000 65000 | sort -R | head -n 1

3
sort -Rไม่สามารถใช้งานบน OS X ได้เช่นกัน
Lri

5

$RANDOMคือตัวเลขระหว่าง 0 ถึง 32767 คุณต้องการพอร์ตระหว่าง 2000 ถึง 65000 พอร์ตเหล่านี้คือ 63001 พอร์ตที่เป็นไปได้ หากเรายึดค่า$RANDOM + 2000ระหว่าง2000ถึง33500เราจะครอบคลุมช่วงพอร์ต 31501 ถ้าเราพลิกเหรียญแล้วมีเงื่อนไขเพิ่ม 31501 เพื่อผลที่เราจะได้รับพอร์ตมากขึ้นจาก33501ไป65001 จากนั้นถ้าเราลดลง 65001 เราจะได้ความครอบคลุมที่แน่นอนด้วยการแจกแจงความน่าจะเป็นแบบเดียวกันสำหรับพอร์ตทั้งหมดดูเหมือนว่า

random-port() {
    while [[ not != found ]]; do
        # 2000..33500
        port=$((RANDOM + 2000))
        while [[ $port -gt 33500 ]]; do
            port=$((RANDOM + 2000))
        done

        # 2000..65001
        [[ $((RANDOM % 2)) = 0 ]] && port=$((port + 31501)) 

        # 2000..65000
        [[ $port = 65001 ]] && continue
        echo $port
        break
    done
}

การทดสอบ

i=0
while true; do
    i=$((i + 1))
    printf "\rIteration $i..."
    printf "%05d\n" $(random-port) >> ports.txt
done

# Then later we check the distribution
sort ports.txt | uniq -c | sort -r

5

คุณสามารถทำได้

cat /dev/urandom|od -N2 -An -i|awk -v f=2000 -v r=65000 '{printf "%i\n", f + r * $1 / 65536}'

หากคุณต้องการรายละเอียดเพิ่มเติมโปรดดูที่Shell Script Generator


เกือบจะ นี่จะให้ช่วง 2,000 ถึง 67000
Ogre Psalm33


3

เอกสาร Bash บอกว่าทุกครั้งที่$RANDOMมีการอ้างอิงจะมีการส่งคืนตัวเลขสุ่มระหว่าง 0 ถึง 32767 หากเรารวมการอ้างอิงสองรายการติดต่อกันเราจะได้รับค่าตั้งแต่ 0 ถึง 65534 ซึ่งครอบคลุมช่วงความเป็นไปได้ 63001 ที่ต้องการสำหรับตัวเลขสุ่มระหว่าง 2000 ถึง 65000

หากต้องการปรับให้เข้ากับช่วงที่แน่นอนเราใช้ผลรวมโมดูโล 63001 ซึ่งจะให้ค่าเราตั้งแต่ 0 ถึง 63000 ซึ่งจะต้องเพิ่มขึ้นอีก 2,000 เพื่อให้ได้ตัวเลขสุ่มที่ต้องการระหว่าง 2000 ถึง 65000 สรุปดังนี้

port=$((((RANDOM + RANDOM) % 63001) + 2000))

การทดสอบ

# Generate random numbers and print the lowest and greatest found
test-random-max-min() {
    max=2000
    min=65000
    for i in {1..10000}; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000))
        echo -en "\r$port"
        [[ "$port" -gt "$max" ]] && max="$port"
        [[ "$port" -lt "$min" ]] && min="$port"
    done
    echo -e "\rMax: $max, min: $min"
}

# Sample output
# Max: 64990, min: 2002
# Max: 65000, min: 2004
# Max: 64970, min: 2000

ความถูกต้องของการคำนวณ

นี่คือการทดสอบแบบเต็มกำลังดุร้ายสำหรับความถูกต้องของการคำนวณ โปรแกรมนี้พยายามสร้างความเป็นไปได้ที่แตกต่างกันทั้งหมด 63001 รายการโดยใช้การคำนวณภายใต้การทดสอบ --jobsพารามิเตอร์ควรจะทำให้มันทำงานได้เร็วขึ้น แต่ก็ไม่ได้กำหนด (รวมของความเป็นไปได้ที่สร้างขึ้นอาจจะต่ำกว่า 63,001)

test-all() {
    start=$(date +%s)
    find_start=$(date +%s)
    total=0; ports=(); i=0
    rm -f ports/ports.* ports.*
    mkdir -p ports
    while [[ "$total" -lt "$2" && "$all_found" != "yes" ]]; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000)); i=$((i+1))
        if [[ -z "${ports[port]}" ]]; then
            ports["$port"]="$port"
            total=$((total + 1))
            if [[ $((total % 1000)) == 0 ]]; then
                echo -en "Elapsed time: $(($(date +%s) - find_start))s \t"
                echo -e "Found: $port \t\t Total: $total\tIteration: $i"
                find_start=$(date +%s)
            fi
        fi
    done
    all_found="yes"
    echo "Job $1 finished after $i iterations in $(($(date +%s) - start))s."
    out="ports.$1.txt"
    [[ "$1" != "0" ]] && out="ports/$out"
    echo "${ports[@]}" > "$out"
}

say-total() {
    generated_ports=$(cat "$@" | tr ' ' '\n' | \sed -E s/'^([0-9]{4})$'/'0\1'/)
    echo "Total generated: $(echo "$generated_ports" | sort | uniq | wc -l)."
}
total-single() { say-total "ports.0.txt"; }
total-jobs() { say-total "ports/"*; }
all_found="no"
[[ "$1" != "--jobs" ]] && test-all 0 63001 && total-single && exit
for i in {1..1000}; do test-all "$i" 40000 & sleep 1; done && wait && total-jobs

สำหรับการพิจารณาว่าต้องมีการวนซ้ำกี่ครั้งเพื่อให้ได้ความน่าจะp/qเป็นที่จะเกิดขึ้นทั้งหมด 63001 ครั้งผมเชื่อว่าเราสามารถใช้นิพจน์ด้านล่างนี้ได้ ยกตัวอย่างเช่นที่นี่คือการคำนวณหามากขึ้นน่าจะเป็นมากกว่า 1/2และที่นี่สำหรับมากกว่า 9/10

การแสดงออก


1
คุณผิด. $RANDOMเป็นจำนวนเต็ม ด้วย "เคล็ดลับ" ของคุณมีค่ามากมายที่จะไม่สามารถบรรลุได้ -1.
gniourf_gniourf

2
ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงด้วย "เป็นจำนวนเต็ม" แต่ถูกต้องอัลกอริทึมผิด การคูณค่าสุ่มจากช่วง จำกัด จะไม่เพิ่มช่วง เราจำเป็นต้องรวมการเข้าถึงสองครั้งเข้า$RANDOMด้วยกันและไม่ต้องปรับโครงสร้างนั้นเป็นการคูณสองโดย$RANDOMควรจะเปลี่ยนทุกการเข้าถึง ฉันได้อัปเดตคำตอบด้วยเวอร์ชันรวมแล้ว

6
การทำRANDOM+RANDOMจะไม่ให้คุณเครื่องแบบกระจายของตัวเลขสุ่มระหว่าง 0 และ 65534.
gniourf_gniourf

3
ถูกต้องกล่าวอีกนัยหนึ่งไม่ใช่จำนวนเงินทั้งหมดที่มีโอกาสเท่ากันที่จะเกิดขึ้น ในความเป็นจริงมันเป็นผายลมถ้าเราตรวจสอบกราฟมันเป็นปิรามิด! ฉันคิดว่านี่เป็นเหตุผลที่ฉันได้รับเวลาการคำนวณที่มากขึ้นกว่าที่คาดไว้โดยสูตรข้างต้น นอกจากนี้ยังมีปัญหากับการใช้งานโมดูโล: จำนวนเงินจาก 63001 ถึง (32767 + 32767) เพิ่มโอกาสในการเกิดขึ้นเป็นสองเท่าสำหรับพอร์ต 2534 แรกเมื่อเปรียบเทียบกับพอร์ตอื่น ๆ ฉันคิดถึงทางเลือก แต่ฉันคิดว่ามันเป็นการดีกว่าที่จะเริ่มจากศูนย์ด้วยคำตอบใหม่ดังนั้นฉันจึงโหวตให้คนนี้ลบ

4
มันเหมือนกับการทอยลูกเต๋าหกเหลี่ยม 2 ลูก ในทางสถิติมันให้เส้นโค้งรูประฆัง: ความน่าจะเป็นต่ำที่จะหมุน "2" หรือ "12" โดยมีความน่าจะเป็นสูงสุดที่จะได้ "7" ที่อยู่ตรงกลาง
Ogre Psalm33


2

PORT=$(($RANDOM%63000+2001)) อยู่ใกล้กับสิ่งที่คุณต้องการฉันคิดว่า

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))หลีกเลี่ยงข้อ จำกัด ด้านขนาดที่ทำให้คุณลำบาก เนื่องจากทุบตีทำให้ไม่มีความแตกต่างระหว่างตัวแปรตัวเลขและตัวแปรสตริงสิ่งนี้ทำงานได้อย่างสมบูรณ์แบบ "number" $RANDOMสามารถต่อกันได้เหมือนกับสตริงและใช้เป็นตัวเลขในการคำนวณ ! ที่น่าตื่นตาตื่นใจ


1
ฉันเห็นสิ่งที่คุณพูด ฉันเห็นด้วยกับการกระจายจะแตกต่างกัน แต่คุณก็ไม่สามารถสุ่มได้จริง มันอาจจะดีกว่าถ้าใช้ $ RANDOM บางครั้งอาจเป็น $ RANDOM $ RANDOM และบางครั้ง $ RANDOM $ RANDOM $ RANDOM เพื่อรับการกระจายที่สม่ำเสมอยิ่งขึ้น มากกว่า $ RANDOMs จะชอบหมายเลขพอร์ตที่สูงกว่าเท่าที่ฉันจะบอกได้
Wastrel

(ฉันลบความคิดเห็นดั้งเดิมของฉันเนื่องจากฉันใช้ค่าตัวเลขผิดและมันสายเกินไปที่จะแก้ไขความคิดเห็น) ขวา. คือประมาณคล้ายกับx=$(( $n%63000 ) x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000
chepner

ฉันจะไม่วิจารณ์ (หรือแม้แต่ทำ) คณิตศาสตร์ ฉันก็ยอมรับมัน นี่คือสิ่งที่ฉันหมายถึง: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); รับ = $ (($ RANDOM% 3)); PORT = $ (($ {จำนวน [$ เลือก]}% 63000 + 2001)) --- ที่ดูเหมือนว่าจะมีปัญหามากมาย ...
ทำลาย

1

คุณสามารถรับตัวเลขสุ่มผ่าน urandom

head -200 /dev/urandom | cksum

เอาท์พุท:

3310670062 52870

เพื่อดึงส่วนหนึ่งของหมายเลขข้างต้น

head -200 /dev/urandom | cksum | cut -f1 -d " "

จากนั้นผลลัพธ์ก็คือ

3310670062

เพื่อตอบสนองความต้องการของคุณ

head -200 /dev/urandom |cksum | cut -f1 -d " " | awk '{print $1%63000+2001}'


0

นี่คือวิธีที่ฉันมักจะสร้างตัวเลขสุ่ม จากนั้นฉันใช้ "NUM_1" เป็นตัวแปรสำหรับหมายเลขพอร์ตที่ฉันใช้ นี่เป็นสคริปต์ตัวอย่างสั้น ๆ

#!/bin/bash

clear
echo 'Choose how many digits you want for port# (1-5)'
read PORT

NUM_1="$(tr -dc '0-9' </dev/urandom | head -c $PORT)"

echo "$NUM_1"

if [ "$PORT" -gt "5" ]
then
clear
echo -e "\x1b[31m Choose a number between 1 and 5! \x1b[0m"
sleep 3
clear
exit 0
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.