So führen Sie einen Befehl 1 aus N mal in Bash

15

Ich möchte, dass ein Befehl zufällig ausgeführt wird, beispielsweise 1 von 10 Mal. Gibt es ein eingebautes oder GNU-Coreutil, idealerweise so etwas wie:

chance 10 && do_stuff

wo do_stuffwird nur 1 in 10 mal ausgeführt? Ich weiß, dass ich ein Drehbuch schreiben kann, aber es scheint ziemlich einfach zu sein und ich habe mich gefragt, ob es einen definierten Weg gibt.

retnikt
quelle
1
Dies ist ein ziemlich guter Indikator dafür, dass Ihr Skript wahrscheinlich zu unhandlich wird, als dass Bash weiterhin eine vernünftige Wahl wäre. Sie sollten eine umfassendere Programmiersprache in Betracht ziehen, möglicherweise eine Skriptsprache wie Python oder Ruby.
Alexander - Wiedereinsetzung von Monica
@Alexander das ist nicht mal wirklich ein script, nur eine zeile. Ich benutze es in einem Cron-Job, um mich
gelegentlich

Antworten:

40

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

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

Die RANDOMSondervariable der Korn-, Bash-, Yash-, Z- und BusyBox-Shells erzeugt bei jeder Auswertung einen pseudozufälligen dezimalen Integer-Wert zwischen 0 und 32767, sodass die obige Option eine Eins-zu-Zehn-Chance ergibt.

Sie können dies verwenden, um eine Funktion zu erzeugen, die sich wie in Ihrer Frage beschrieben verhält, zumindest in Bash:

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

Das Vergessen, ein Argument oder ein ungültiges Argument anzugeben, führt zu einem Ergebnis von 1, also chance && do_stuffniemals do_stuff.

Hierbei wird die allgemeine Formel für "1 in n " verwendet $RANDOM, [[ $RANDOM -lt $((32767 / n + 1)) ]]wobei 32768 eine Chance von (⎣32767 / n ⎦ + 1) ergibt. Werte, bei ndenen es sich nicht um Faktoren von 32768 handelt, führen aufgrund der ungleichmäßigen Aufteilung des Bereichs möglicher Werte zu einer Verzerrung.

Stephen Kitt
quelle
(1/2) Beim erneuten Aufrufen dieser Ausgabe: Es ist intuitiv, dass es eine Obergrenze für die Anzahl der Teile geben sollte, es ist beispielsweise nicht möglich, 40.000 Teile zu haben. Tatsächlich ist die tatsächliche Grenze viel kleiner. Das Problem ist, dass die Ganzzahldivision nur eine Annäherung an die Größe jedes Teils ist. Es ist erforderlich, dass zwei aufeinanderfolgende Teile zu einer Differenz des Limits führen, $((32767/parts+1))die größer als 1 ist. Andernfalls besteht die Gefahr, dass sich die Anzahl der Teile um 1 erhöht, während das Ergebnis der Division (und damit das Limit) gleich ist. (Fortsetzung)
Isaac
(2/2) (Forts.) Das wird mehr Zahlen ausmachen, als tatsächlich verfügbar sind. Die Formel dafür lautet (32767/n-32767/(n+1))>=1, nach n aufzulösen, was eine Grenze bei ~ 181,5 ergibt. Tatsächlich könnte die Anzahl der Teile ohne Probleme bis zu 194 betragen. Bei 195 Teilen beträgt die resultierende Grenze 151, das gleiche Ergebnis wie bei 194 Teilen. Das ist inkongruent und sollte vermieden werden. Kurz gesagt, die Obergrenze für die Anzahl der Teile (n) sollte 194 sein. Sie können die Grenzprüfungen durchführen:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Sonderlösung:

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

Überprüfen Sie, ob die letzte Ziffer der aktuellen Zeit in Nanosekunden 1 ist!

Stackzebra
quelle
Das ist großartig.
Eric Duminil
2
Sie müssen sicherstellen, dass der Anruf [ $(date +%1N) == 1 ] && do_stuffnicht in regelmäßigen Abständen erfolgt, da sonst die Zufälligkeit beeinträchtigt wird. Stellen Sie sich while true; do [ $(date +1%N) == 1 ] && sleep 1; doneein abstraktes Gegenbeispiel vor. Trotzdem ist die Idee, mit den Nanosekunden zu spielen, wirklich gut. Ich denke, ich werde sie verwenden, daher +1
XavierStuvw
3
@ XavierStuvw Ich denke, Sie hätten einen Punkt, wenn der Code Sekunden überprüft wurde. Aber Nanosekunden? Es sollte wirklich zufällig erscheinen.
Eric Duminil
9
@EricDuminil: Leider: 1) Die Systemuhr ist vertraglich nicht verpflichtet, eine tatsächliche Auflösung von Nanosekunden bereitzustellen (sie kann auf die nächste gerundet werden clock_getres()). 2) Es ist dem Scheduler nicht untersagt, beispielsweise immer eine Zeitscheibe an einer 100-Nanosekunden-Grenze zu starten (was zu einer Verzerrung führen würde, selbst wenn die Uhr stimmt). 3) Der unterstützte Weg, zufällige Werte zu erhalten, besteht (normalerweise) darin, /dev/urandomden Wert von auszulesen oder zu untersuchen $RANDOM.
Kevin
1
@ Kevin: Vielen Dank für den Kommentar.
Eric Duminil
21

Eine Alternative zur Verwendung $RANDOMist der shufBefehl:

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

werde den Job machen. Auch nützlich für die zufällige Auswahl von Zeilen aus einer Datei, z. für eine Musikwiedergabeliste.

seumasmac
quelle
1
Ich kannte diesen Befehl nicht. Sehr schön!
Eisenknurr
12

Verbessern Sie die erste Antwort und machen Sie deutlich, was Sie erreichen möchten:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
quelle
1
$RANDOM % 10wird eine Verzerrung haben, es sei denn, die $RANDOMerzeugt genau ein Vielfaches von 10 verschiedenen Werten (was in der Regel nicht in einem Binärcomputer vorkommt)
phuclv
1
@phuclv Ja, es hat die gleiche Tendenz wie "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Ja, die Vorspannung ist identisch, 0,100006104 anstelle von 0,1 für 1 zu 10 (beim Prüfen gegen 0).
Stephen Kitt
1

Ich bin mir nicht sicher, ob Sie Zufälligkeit oder Periodizität wollen ... Für Periodizität:

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

Sie können es mit dem obigen "$ RANDOM" -Trick mischen, um etwas chaotischeres zu erzeugen, zum Beispiel:

denn ich in seq 1 1000 $RANDOM; mache ein Echo $ i; fertig

HTH :-)

Dr_ST
quelle