Cómo ejecutar un comando 1 de N veces en Bash

15

Quiero una forma de ejecutar un comando al azar, digamos 1 de cada 10 veces. ¿Hay un coreutil incorporado o GNU para hacer esto, idealmente algo como:

chance 10 && do_stuff

donde do_stuffsolo se ejecuta 1 de cada 10 veces? Sé que podría escribir un guión, pero parece algo bastante simple y me preguntaba si hay una forma definida.

retnikt
fuente
1
Este es un indicador bastante bueno de que su script probablemente se está yendo demasiado de las manos para que bash continúe siendo una opción razonable. Debería considerar un lenguaje de programación más completo, tal vez un lenguaje de script como Python o Ruby.
Alexander - Restablece a Monica el
@Alexander esto ni siquiera es realmente un guión, solo una línea. Lo estoy usando en un cron-job para notificarme al azar de vez en cuando como un recordatorio para hacer algo
retnikt

Respuestas:

40

En ksh, Bash, Zsh, Yash u BusyBox sh:

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

La RANDOMvariable especial de los shells Korn, Bash, Yash, Z y BusyBox produce un valor entero decimal pseudoaleatorio entre 0 y 32767 cada vez que se evalúa, por lo que lo anterior da (cerca de) una posibilidad de uno en diez.

Puede usar esto para producir una función que se comporte como se describe en su pregunta, al menos en Bash:

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

Olvidar proporcionar un argumento, o proporcionar un argumento no válido, producirá un resultado de 1, por chance && do_stufflo que nunca lo hará do_stuff.

Esto usa la fórmula general para "1 en n " usando$RANDOM , que es [[ $RANDOM -lt $((32767 / n + 1)) ]], dando un (⎣32767 / n ⎦ + 1) en 32768 posibilidades. Los valores de los ncuales no son factores de 32768 introducen un sesgo debido a la división desigual en el rango de valores posibles.

Stephen Kitt
fuente
(1/2) Al volver a visitar este tema: es intuitivo que debe haber un límite superior en el número de partes, por ejemplo, no es posible tener 40,000 partes. En realidad, el límite real es bastante más pequeño. El problema es que la división entera es solo una aproximación a cuán grande podría ser cada parte. Se requiere que dos partes consecutivas $((32767/parts+1))den como resultado que una diferencia del límite sea ​​mayor que 1 o corremos el riesgo de que el número de partes aumente en 1 mientras el resultado de la división (y, por lo tanto, el límite) sea el mismo. (cont.)
Isaac
(2/2) (cont.) Eso representará más números de los que están realmente disponibles. La fórmula para eso es (32767/n-32767/(n+1))>=1resolver para n que da un límite de ~ 181.5. De hecho, el número de piezas podría correr hasta 194 sin problemas. Pero con 195 partes, el límite resultante es 151, el mismo resultado que con 194 partes. Eso es incongruente y debe evitarse. En resumen, el límite superior para el número de partes (n), debería ser 194. Podría hacer las pruebas de límite:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Solución no estándar:

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

¡Compruebe si el último dígito de la hora actual en nanosegundos es 1!

stackzebra
fuente
Eso es genial.
Eric Duminil
2
Debe asegurarse de que la llamada [ $(date +%1N) == 1 ] && do_stuffno se produce a intervalos regulares, de lo contrario, la aleatoriedad se echa a perder. Piense while true; do [ $(date +1%N) == 1 ] && sleep 1; doneen un contraejemplo abstracto. Sin embargo, la idea de jugar con los nanosegundos es realmente buena, creo que la
usaré
3
@XavierStuvw Creo que tendría razón si el código estuviera revisando segundos. ¿Pero nanosegundos? Debería aparecer realmente al azar.
Eric Duminil
99
@EricDuminil: Desafortunadamente: 1) El reloj del sistema no está obligado contractualmente a proporcionar una resolución real de nanosegundos (puede redondearse al más cercano clock_getres()), 2) No se prohíbe al programador, por ejemplo, comenzar siempre un intervalo de tiempo en un límite de 100 nanosegundos (lo que introduciría un sesgo incluso si el reloj es correcto), 3) La forma compatible de obtener valores aleatorios es (generalmente) leer /dev/urandomo inspeccionar el valor de $RANDOM.
Kevin
1
@ Kevin: Muchas gracias por el comentario.
Eric Duminil
21

Una alternativa al uso $RANDOMes el shufcomando:

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

Hará el trabajo. También es útil para seleccionar al azar líneas de un archivo, por ejemplo. para una lista de reproducción de música.

seumasmac
fuente
1
No conocía ese comando. ¡Muy agradable!
Eisenknurr
12

Mejorando la primera respuesta y haciendo mucho más obvio lo que está tratando de lograr:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
fuente
1
$RANDOM % 10tendrá un sesgo, a menos que la $RANDOMproduce exactamente un múltiplo de 10 valores diferentes (que generalmente no ocurre en un ordenador binario)
phuclv
1
@phuclv Sí, tendrá el mismo sesgo que "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Sí, el sesgo es idéntico, 0.100006104 en lugar de 0.1 para 1 en 10 (cuando se compara con 0).
Stephen Kitt
1

No estoy seguro si quieres aleatoriedad o periodicidad ... Para periodicidad:

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

Puedes mezclarlo con el truco "$ RANDOM" anterior para producir algo más caótico, por ejemplo:

porque yo en seq 1 1000 $RANDOM ; do echo $ i; hecho

HTH :-)

Dr_ST
fuente