Bashスクリプトの範囲からの乱数


196

2000-65000シェルスクリプトからランダムなポート番号を生成する必要があります。問題は$RANDOM15ビットの数値なので、行き詰まっています!

PORT=$(($RANDOM%63000+2001)) サイズの制限がなければ、うまく機能します。

誰かが私がこれを行う方法の例を持っていますか、おそらく/dev/urandom範囲から何かを抽出してそれを取得することによって?

回答:


398
shuf -i 2000-65000 -n 1

楽しい!

編集:範囲は包括的です。


7
shufは比較的最近のことだと思います-この2、3年はUbuntuシステムで見ましたが、現在のRHEL / CentOSでは見ていません。
Cascabel

5
また、この用途にはおそらく問題ありshufませんが、実際には入力全体を置換します。これは、非常に頻繁に乱数を生成する場合には、悪い選択です。
Cascabel 2010年

3
@Jefromi:私のシステムでは、このテストtime for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doneを使用して比較end=1するend=65535と、短い範囲で約25%の改善が見られ、100万回の反復で約4秒の差がありました。そして、それはだたくさん速くOPのバッシュ計算万回を実行するよりも。
追って通知があるまで一時停止。

8
@Dennis Williamson:でテストを実行して-n 1も、時間差はごくわずかend=4000000000です。知っておくとshuf
便利

6
Macにshufがありません:(
Viren

79

Mac OS XおよびFreeBSDでは、jotを使用することもできます。

jot -r 1  2000 65000

5
この例でjotは、間隔の最小値と最大値(つまり、2000と65000)が不公平に分布しています。つまり、最小値と最大値が生成される頻度が低くなります。詳細と回避策については、私の回答を参照してください。
Clint Pachl 2015年

jotほとんどのGNU / Linuxディストリビューションでも利用可能
Thor

43

bashのmanページによると、0〜32767の$RANDOM範囲で配布されています。つまり、符号なし15ビット値です。$RANDOM均一に分散されていると仮定すると、次のように均一に分散された符号なし30ビット整数を作成できます。

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

あなたの範囲は2の累乗ではないので、単純なモジュロ演算はほぼ均一な分布を与えるだけですが、あなたの場合のように、30ビットの入力範囲と16ビット未満の出力範囲があります、これは本当に十分に近いはずです:

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

1
変数$RANDOMはすべてのシェルで常に使用できるわけではありません。別の解決策を探している
Lukas Liesis

これを正しく理解していれば、1,000,000,000の範囲内で32,000の数値を分散しています。ただし、2 ^ 15の倍数でのみヒットします。1から2 ^ 30までのすべての桁を均等に埋めるのではなく、2 ^ 15でスキップカウントします。これは、均一な分布です。
同型

@isomorphismesコードが$RANDOM2回参照することに注意してください。をサポートするシェル$RANDOMでは、参照されるたびに新しい値が生成されます。したがって、このコードはビット0から14を1つの$RANDOM値で埋め、ビット15から29を別の値で埋めます。$RANDOM均一で独立していると仮定すると、これは何もスキップせずに0から2 ** 30-1までのすべての値をカバーします。
イエシン

41

そして、これはPythonを使ったものです

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

そしてawkを持つもの

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

6
これは私から賛成票をもらいます。私はさまざまなシステム用のbashスクリプトを書いており、awkはおそらくその仕事で最も豊富なツールだと思います。問題なくmac os xとcentosで動作しましたが、私のdebianマシン、およびおそらく他の通常の* nixマシンでも動作することを知っています。
John Hunt

6
ただし、awkのランダムシードは毎秒1回しか更新されないようです。そのため、a)すべてのコストを回避するか、b)シードを再初期化します。
ジョンハント

+1は、これがコンパイルなしの唯一のPOSIXの可能性であると思われるためです。POSIXではRANDOM保証されません
Ciro Santilli郝海东冠状病六四事件法轮功

この-Sオプションを使用すると、になりますImportError: No module named random。削除すれば動作します。そのためのゴーストドッグの意図がわからない。
クリスジョンソン

1
python -S -c "import random; print random.randrange(2000,63000)"正常に動作するようです。しかし、1と2の間の乱数を取得しようとすると、常に1を取得するようです。
HubertLéveilléGauvin 2017

17

頭に浮かぶ最も簡単な一般的な方法は、perlのワンライナーです。

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

常に2つの数値を使用できます。

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

あなたはまだあなたの範囲にクリップする必要があります。これは一般的なnビットの乱数メソッドではありませんが、あなたのケースで機能し、すべてbash内にあります。

本当にかわいくて/ dev / urandomから読みたい場合は、次のようにします。

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

これは2バイトを読み取り、それらをunsigned intとして出力します。あなたはまだあなたのクリッピングを行う必要があります。


私はこの手法を使用しましたが、時々、番号が生成されず、単に空白スペースになることに気付きました。
PdC

perlがインストールされている必要があります。すべてではないにしてもほとんどのLinuxマシンで実行できるスクリプトを作成します。awk別の回答のバージョンに固執します
Lukas Liesis

乱数を追加すると、低または高を犠牲にして中間結果が優先されます。一様にランダムではありません。
同型

@isomorphismesはい、文字通り2つの乱数を追加するだけであれば。しかし、ここで2番目の式を参照しているとすると、それはそれがしていることではありません。[0,32767]の乱数と、次のビットの独立したランダムな選択肢、つまり0または32768です。均一です。(ただし、リローリングで範囲をクリップする必要があるため、元の質問には理想的ではありません。)
Cascabel

7

あなたがbashの専門家ではなく、これをLinuxベースのbashスクリプトの変数に入れようとしている場合は、次のことを試してください。

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

これにより$VAR、200〜700の範囲がに含まれます。


5

ここに別のものがあります。私はそれがほとんど何でもうまくいくと思っていましたが、仕事の私のセントボックスでソートのランダムオプションは利用できません。

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

3
sort -ROS Xでも利用できません。
Lri

5

$RANDOMは0〜32767の数値です。2000〜65000のポートが必要です。これらは63001の可能なポートです。我々はの値に固執する場合は$RANDOM + 2000間に200033500、我々は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

ルビーと同じ:

echo $(ruby -e 'puts rand(20..65)') #=> 65 (inclusive ending)
echo $(ruby -e 'puts rand(20...65)') #=> 37 (exclusive ending)

3

Bashのドキュメントでは$RANDOM参照されるたびに0〜32767の乱数が返されると記載されています。2つの連続した参照を合計すると、0〜65534の値が得られます。これは、2000〜65000の乱数の63001の可能性の望ましい範囲をカバーします。

これを正確な範囲に調整するには、63001を法とする合計を使用します。これにより、0〜63000の値が得られます。これは、2000から65000の範囲の目的の乱数を提供するために、2000だけ増分する必要があります。これは、次のように要約されます。

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パラメーターは実行速度を上げる必要がありますが、確定的ではありません(生成される可能性の合計は63001よりも低い場合があります)。

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/q63001の可能性がすべて生成される確率を得るのに必要な反復回数を決定するには、以下の式を使用できると思います。たとえば、ここでは1/2より大きい確率の計算がありここでは9/10より大きい確率があります

表情


1
あなたが間違っている。$RANDOM整数です。「トリック」には、決して達成できない多くの価値があります。-1
gniourf_gniourf 2012

2
「整数である」の意味がわかりませんが、正しいアルゴリズムは間違っていました。制限された範囲からランダムな値を乗算しても、範囲は増えません$RANDOM代わりにへの2つのアクセスを合計する必要があり、アクセス$RANDOMごとに変更されることになっているので、2による乗算にそれをリファクタリングしないでください。サムバージョンで答えを更新しました。

6
実行すると、RANDOM+RANDOMあなたを与えることはありません均一 0と65534の間で乱数の分布を
gniourf_gniourf

3
正解、言い換えれば、すべての合計が発生する可能性が等しいわけではありません。実際、それとはほど遠いです。グラフを確認すると、ピラミッドです!これが、上記の計算式で予想される計算時間よりもかなり長い計算時間を費やしている理由だと思います。モジュロ演算にも問題があります。63001から(32767 + 32767)の合計は、残りのポートと比較して、最初の2534ポートの発生確率を2倍にします。私は代替案を考えていましたが、新しい答えからゼロから始めた方がいいと思うので、これを削除のために投票します。

4
2つの6面サイコロを振るようなものです。統計的にはベルカーブが得られます。「2」または「12」を振る可能性は低く、途中で「7」を得る可能性が最も高くなります。
Ogre Psalm33 2013


2

PORT=$(($RANDOM%63000+2001)) あなたが望むものに近いです。

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))あなたを困らせるサイズ制限を回避します。bashは数値変数と文字列変数を区別しないため、これは完全にうまく機能します。「数値」$RANDOMは文字列のように連結でき、計算で数値として使用できます。すごい!


1
あなたの言っていることがわかります。分布が異なることに同意しますが、とにかく真のランダム性を得ることができません。より均等な分布を得るために、$ RANDOM、$ RANDOM $ RANDOM、および$ RANDOM $ RANDOM $ RANDOMを使用する方が良い場合があります。$ RANDOMが多いほど、私が知る限り、ポート番号が大きくなります。
Wastrel、2012

(間違った数値を使用したため、コメントを編集するには遅すぎたため、元のコメントを削除しました)。正しい。x=$(( $n%63000 )はとほぼ同じx=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000です。
chepner

数学を批判するつもりはありませんでした(または行うことすらしませんでした)。私は単にそれを受け入れました。これが私が言ったことです:num =($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $(($ RANDOM%3)); PORT = $(($ {num [$ pick]}%63000 + 2001))---これは大変な問題のようです...
Wastrel

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.