Cara menjalankan perintah 1 dari N kali dalam Bash

15

Saya ingin cara menjalankan perintah secara acak, katakan 1 dari 10 kali. Apakah ada builtin atau GNU coreutil untuk melakukan ini, idealnya kira-kira seperti:

chance 10 && do_stuff

di mana do_stuffhanya dieksekusi 1 dari 10 kali? Saya tahu saya bisa menulis naskah, tetapi sepertinya hal yang cukup sederhana dan saya bertanya-tanya apakah ada cara yang pasti.

Retnikt
sumber
1
Ini adalah indikator yang cukup bagus bahwa skrip Anda mungkin menjadi terlalu tak terkendali untuk bash untuk terus menjadi pilihan yang masuk akal. Anda harus mempertimbangkan bahasa pemrograman yang lebih lengkap, mungkin bahasa scripting seperti Python atau Ruby.
Alexander - Reinstate Monica
@Alexander ini bahkan bukan script, hanya satu baris. Saya menggunakannya dalam cron-job untuk memberi tahu saya secara acak setiap sekarang dan kemudian sebagai pengingat untuk melakukan sesuatu
retnikt

Jawaban:

40

Di ksh, Bash, Zsh, Yash atau BusyBox sh:

[ "$RANDOM" -lt 3277 ] && do_stuff

The RANDOMvariabel khusus dari Korn, Bash, Yash, Z dan kerang BusyBox menghasilkan nilai integer desimal pseudo-acak antara 0 dan 32.767 setiap kali itu dievaluasi, sehingga di atas memberikan (dekat) satu-in-sepuluh kesempatan.

Anda dapat menggunakan ini untuk menghasilkan fungsi yang berperilaku seperti yang dijelaskan dalam pertanyaan Anda, setidaknya di Bash:

function chance {
  [[ -z $1 || $1 -le 0 ]] && return 1
  [[ $RANDOM -lt $((32767 / $1 + 1)) ]]
}

Lupa memberikan argumen, atau memberikan argumen yang tidak valid, akan menghasilkan hasil 1, jadi chance && do_stufftidak akan pernah do_stuff.

Ini menggunakan rumus umum untuk penggunaan “1 in n$RANDOM, yaitu [[ $RANDOM -lt $((32767 / n + 1)) ]], memberikan (⎣32767 / n ⎦ + 1) pada peluang 32768. Nilai nyang bukan merupakan faktor 32768 menimbulkan bias karena perbedaan yang tidak merata dalam rentang nilai yang mungkin.

Stephen Kitt
sumber
(1/2) Saat mengunjungi kembali masalah ini: Sangat intuitif bahwa harus ada batas atas jumlah komponen, misalnya, tidak mungkin memiliki 40.000 komponen. Sebenarnya batas sebenarnya cukup kecil. Masalahnya adalah bahwa divisi integer hanyalah perkiraan seberapa besar masing-masing bagian. Diperlukan bahwa dua bagian berturut-turut menghasilkan perbedaan batas $((32767/parts+1))lebih besar dari 1 atau kita menghadapi risiko memiliki jumlah bagian meningkat dalam 1 sedangkan hasil pembagian (dan karenanya batas) sama. (lanjutan ..)
Isaac
(2/2) (lanjutan ..) Itu akan menghitung lebih banyak angka daripada yang sebenarnya tersedia. Rumus untuk itu adalah (32767/n-32767/(n+1))>=1, penyelesaian untuk n yang memberikan batas pada ~ 181,5. Bahkan jumlah bagian bisa berjalan hingga 194 tanpa masalah. Tetapi pada 195 bagian, batas yang dihasilkan adalah 151, hasil yang sama dengan 194 bagian. Itu tidak sesuai dan harus dihindari. Singkatnya, batas atas untuk jumlah bagian (n), harus 194. Anda bisa melakukan tes batas:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Solusi non-standar:

[ $(date +%1N) == 1 ] && do_stuff

Periksa apakah digit terakhir dari waktu saat ini dalam nanodetik adalah 1!

stackzebra
sumber
Itu luar biasa.
Eric Duminil
2
Anda harus memastikan bahwa panggilan [ $(date +%1N) == 1 ] && do_stufftidak terjadi secara berkala, jika tidak, keacakannya rusak. Pikirkan while true; do [ $(date +1%N) == 1 ] && sleep 1; donesebagai contoh tandingan abstrak. Meskipun demikian, gagasan bermain dengan nanodetik sangat bagus, saya pikir saya akan menggunakannya, maka +1
XavierStuvw
3
@ XavierStuvw Saya pikir Anda akan ada benarnya jika kode itu memeriksa detik. Tapi nanodetik? Seharusnya terlihat sangat acak.
Eric Duminil
9
@EricDuminil: Sayangnya: 1) Jam sistem tidak diwajibkan secara kontrak untuk memberikan resolusi nanosecond aktual (mungkin membulatkan ke terdekat clock_getres()), 2) Penjadwal tidak dilarang, misalnya, selalu memulai kutu waktu pada batas 100-nanosecond (yang akan menimbulkan bias bahkan jika jamnya benar), 3) Cara yang didukung untuk mendapatkan nilai acak adalah (biasanya) membaca dari /dev/urandomatau memeriksa nilai $RANDOM.
Kevin
1
@Kevin: Terima kasih banyak atas komentarnya.
Eric Duminil
21

Alternatif untuk menggunakan $RANDOMadalah shufperintah:

[[ $(shuf -i 1-10 -n 1) == 1 ]] && do_stuff

akan melakukan pekerjaan itu. Juga berguna untuk memilih garis secara acak dari suatu file, mis. untuk daftar putar musik.

seumasmac
sumber
1
Tidak tahu perintah itu. Sangat bagus!
Eisenknurr
12

Memperbaiki jawaban pertama dan membuatnya lebih jelas apa yang ingin Anda capai:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
sumber
1
$RANDOM % 10akan memiliki bias, kecuali $RANDOMmenghasilkan kelipatan tepat dari 10 nilai yang berbeda (yang umumnya tidak terjadi di komputer biner)
phuclv
1
@ phuclv Ya, itu akan memiliki bias yang sama dengan "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Ya, biasnya identik, 0,100006104 alih-alih 0,1 untuk 1-in-10 (saat memeriksa 0).
Stephen Kitt
1

Saya tidak yakin apakah Anda menginginkan keacakan atau periodisitas ... Untuk periodisitas:

for i in `seq 1 10 100`; do echo $i;done
1
11
21
31
41
51
61
71
81
91

Anda dapat mencampurnya dengan trik "$ RANDOM" di atas untuk menghasilkan sesuatu yang lebih kacau, misalnya:

untuk saya masuk seq 1 1000 $RANDOM; lakukan echo $ i; dilakukan

HTH :-)

Dr_ST
sumber