Come eseguire un comando 1 su N volte in Bash


15

Voglio un modo per eseguire un comando in modo casuale, per esempio 1 su 10 volte. Esiste un coreutil incorporato o GNU per fare questo, idealmente qualcosa del tipo:

chance 10 && do_stuff

dove do_stuffviene eseguito solo 1 su 10 volte? So che potrei scrivere una sceneggiatura, ma sembra una cosa abbastanza semplice e mi chiedevo se esiste un modo definito.


1
Questo è un buon indicatore del fatto che il tuo script probabilmente sta diventando troppo fuori mano per bash per continuare ad essere una scelta ragionevole. Dovresti considerare un linguaggio di programmazione più completo, forse un linguaggio di scripting come Python o Ruby.
Alexander - Ripristina Monica il

@Alexander questa non è nemmeno una sceneggiatura, solo una riga. Lo sto usando in un cron-job per avvisarmi a caso ogni tanto come promemoria per fare qualcosa
retnikt

Risposte:


40

In ksh, Bash, Zsh, Yash o BusyBox sh:

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

La RANDOMvariabile speciale delle shell Korn, Bash, Yash, Z e BusyBox produce un valore intero decimale pseudo-casuale compreso tra 0 e 32767 ogni volta che viene valutato, quindi quanto sopra offre (quasi) una possibilità su dieci.

Puoi usarlo per produrre una funzione che si comporti come descritto nella tua domanda, almeno in Bash:

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

Dimenticare di fornire un argomento o fornire un argomento non valido produrrà un risultato di 1, quindi chance && do_stuffmai do_stuff.

Questo usa la formula generale per “1 in n ” usando $RANDOM, ovvero [[ $RANDOM -lt $((32767 / n + 1)) ]]dando una probabilità (⎣32767 / n ⎦ + 1) in 32768. I valori nche non sono fattori di 32768 introducono una distorsione a causa della divisione irregolare nell'intervallo di valori possibili.


(1/2) Ripensando a questo problema: è intuitivo che ci sia un limite superiore al numero di parti, ad esempio non è possibile avere 40.000 parti. In realtà il limite reale è piuttosto ridotto. Il problema è che la divisione dei numeri interi è solo un'approssimazione della grandezza di ciascuna parte. È necessario che due parti consecutive comportino una differenza del limite $((32767/parts+1))maggiore di 1 o si corre il rischio di aumentare il numero di parti in 1 mentre il risultato della divisione (e quindi il limite) è lo stesso. (cont.)
Isaac,

(2/2) (cont.) Ciò rappresenterà più numeri di quelli effettivamente disponibili. La formula per questo è (32767/n-32767/(n+1))>=1, risolvendo per n che dà un limite a ~ 181,5. In effetti il ​​numero di parti potrebbe arrivare a 194 senza problemi. Ma a 195 parti, il limite risultante è 151, lo stesso risultato di 194 parti. Questo è incongruente e dovrebbe essere evitato. In breve, il limite superiore per il numero di parti (n), dovrebbe essere 194. È possibile effettuare i test limite:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isacco

25

Soluzione non standard:

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

Controlla se l'ultima cifra dell'ora corrente in nanosecondi è 1!


È fantastico
Eric Duminil,

2
Devi assicurarti che la chiamata [ $(date +%1N) == 1 ] && do_stuffnon avvenga a intervalli regolari, altrimenti la casualità è viziata. Pensa while true; do [ $(date +1%N) == 1 ] && sleep 1; donea un controesempio astratto. Tuttavia, l'idea di giocare con i nanosecondi è davvero buona, penso che la userò, quindi +1
XavierStuvw

3
@XavierStuvw Penso che avresti un punto se il codice stesse controllando i secondi. Ma i nanosecondi? Dovrebbe apparire davvero casuale.
Eric Duminil,

9
@EricDuminil: Sfortunatamente: 1) L'orologio di sistema non è contrattualmente obbligato a fornire la risoluzione effettiva dei nanosecondi (può arrotondare al più vicino clock_getres()), 2) Lo scheduler non può, ad esempio, avviare sempre un intervallo di tempo su un limite di 100 nanosecondi (che introdurrebbe pregiudizi anche se l'orologio è corretto), 3) Il modo supportato per ottenere valori casuali è (di solito) leggere /dev/urandomo ispezionare il valore di $RANDOM.
Kevin,

1
@ Kevin: Grazie mille per il commento.
Eric Duminil,

21

Un'alternativa all'utilizzo $RANDOMè il shufcomando:

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

farà il lavoro. Utile anche per la selezione casuale di linee da un file, ad es. per una playlist musicale.


1
Non conoscevo quel comando. Molto bella!
Eisenknurr,

12

Migliorare la prima risposta e rendere molto più ovvio ciò che stai cercando di ottenere:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"

1
$RANDOM % 10avrà una propensione, a meno che non $RANDOMproduca esattamente un multiplo di 10 valori diversi (che generalmente non accade in un computer binario)
phuclv

1
@phuclv Sì, avrà lo stesso pregiudizio di "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr,

Sì, il bias è identico, 0,100006104 invece di 0,1 per 1 su 10 (quando si verifica contro 0).
Stephen Kitt,

1

Non sono sicuro che tu voglia casualità o periodicità ... Per periodicità:

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

Puoi mescolarlo con il trucco "$ RANDOM" sopra per produrre qualcosa di più caotico, ad esempio:

per me dentro seq 1 1000 $RANDOM; echo $ i; fatto

HTH :-)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.