Comment exécuter une commande 1 fois sur N fois dans Bash

15

Je veux un moyen d'exécuter une commande au hasard, disons 1 fois sur 10. Existe-t-il un coreutil intégré ou GNU pour ce faire, idéalement quelque chose comme:

chance 10 && do_stuff

do_stuffest exécuté seulement 1 fois sur 10? Je sais que je pourrais écrire un script, mais cela semble être une chose assez simple et je me demandais s'il y avait une manière définie.

retnikt
la source
1
C'est un assez bon indicateur que votre script devient probablement trop incontrôlable pour que bash continue d'être un choix raisonnable. Vous devriez envisager un langage de programmation à part entière, peut-être un langage de script comme Python ou Ruby.
Alexander - Reinstate Monica
@Alexander ce n'est même pas vraiment un script, juste une ligne. Je l'utilise dans un cron-job pour m'avertir au hasard de temps en temps comme rappel pour faire quelque chose
retnikt

Réponses:

40

Dans ksh, Bash, Zsh, Yash ou BusyBox sh:

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

le RANDOM variable spéciale des shells Korn, Bash, Yash, Z et BusyBox produit une valeur entière décimale pseudo-aléatoire entre 0 et 32767 chaque fois qu'elle est évaluée, de sorte que ce qui précède donne (presque) une chance sur dix.

Vous pouvez l'utiliser pour produire une fonction qui se comporte comme décrit dans votre question, au moins dans Bash:

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

Oublier de fournir un argument, ou fournir un argument non valide, produira un résultat de 1, donc chance && do_stuffne sera jamaisdo_stuff .

Celui-ci utilise la formule générale pour «1 dans n » en utilisant $RANDOM, ce qui [[ $RANDOM -lt $((32767 / n + 1)) ]]donne un (⎣32767 / n ⎦ + 1) dans 32768 chance. Les valeurs nqui ne sont pas des facteurs de 32768 introduisent un biais en raison de la répartition inégale dans la plage de valeurs possibles.

Stephen Kitt
la source
(1/2) En revisitant ce problème: Il est intuitif qu'il devrait y avoir une limite supérieure sur le nombre de pièces, il n'est pas possible d'avoir 40.000 pièces, par exemple. En fait, la limite réelle est bien plus petite. Le problème est que la division entière n'est qu'une approximation de la taille de chaque partie. Il est nécessaire que deux parties consécutives entraînent une différence de la limite $((32767/parts+1))supérieure à 1 ou nous courons le risque de voir le nombre de parties augmenter en 1 alors que le résultat de la division (et donc la limite) est le même. (suite)
Isaac
(2/2) (suite). Cela représentera plus de chiffres que ceux qui sont réellement disponibles. La formule pour cela est la (32767/n-32767/(n+1))>=1résolution de n qui donne une limite à ~ 181,5. En fait, le nombre de pièces pourrait atteindre 194 sans problème. Mais à 195 parties, la limite résultante est de 151, le même résultat qu'avec 194 parties. C'est incongru et devrait être évité. En bref, la limite supérieure pour le nombre de pièces (n), devrait être 194. Vous pouvez faire les tests de limite:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Solution non standard:

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

Vérifiez si le dernier chiffre de l'heure actuelle en nanosecondes est 1!

stackzebra
la source
C'est génial.
Eric Duminil
2
Vous devez vous assurer que l'appel [ $(date +%1N) == 1 ] && do_stuffne se produit pas à intervalles réguliers, sinon le caractère aléatoire est gâché. Considérez-le while true; do [ $(date +1%N) == 1 ] && sleep 1; donecomme un contre-exemple abstrait. Néanmoins, l'idée de jouer avec les nanosecondes est vraiment bonne, je pense que je vais l'utiliser, donc +1
XavierStuvw
3
@XavierStuvw Je pense que vous auriez raison si le code vérifiait les secondes. Mais les nanosecondes? Cela devrait paraître vraiment aléatoire.
Eric Duminil
9
@EricDuminil: Malheureusement: 1) L'horloge système n'est pas contractuellement obligée de fournir une résolution réelle en nanosecondes (elle peut arrondir au plus proche clock_getres()), 2) L'ordonnanceur n'est pas interdit, par exemple, de toujours démarrer une tranche de temps sur une limite de 100 nanosecondes (ce qui introduirait un biais même si l'horloge est correcte), 3) La méthode prise en charge pour obtenir des valeurs aléatoires est (généralement) de lire /dev/urandomou d'inspecter la valeur de $RANDOM.
Kevin
1
@Kevin: Merci beaucoup pour le commentaire.
Eric Duminil
21

Une alternative à l'utilisation $RANDOMest la shufcommande:

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

fera le travail. Également utile pour sélectionner au hasard des lignes dans un fichier, par exemple. pour une playlist de musique.

seumasmac
la source
1
Je ne connaissais pas cette commande. Très agréable!
Eisenknurr
12

Améliorer la première réponse et rendre beaucoup plus évident ce que vous essayez de réaliser:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
la source
1
$RANDOM % 10aura un biais, à moins que le $RANDOMproduit exactement un multiple de 10 valeurs différentes (ce qui ne se produit généralement pas dans un ordinateur binaire)
phuclv
1
@phuclv Oui, il aura le même biais que "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Oui, le biais est identique, 0.100006104 au lieu de 0.1 pour 1 sur 10 (lors de la comparaison avec 0).
Stephen Kitt
1

Je ne sais pas si vous voulez du hasard ou de la périodicité ... Pour la périodicité:

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

Vous pouvez le mélanger avec l'astuce "$ RANDOM" ci-dessus pour produire quelque chose de plus chaotique, par exemple:

car je suis seq 1 1000 $RANDOM ; faire écho $ i; fait

HTH :-)

Dr_ST
la source