Jak uruchomić polecenie 1 z N razy w Bash

15

Chcę sposób losowego uruchomienia polecenia, powiedz 1 na 10 razy. Czy jest do tego wbudowany lub GNU coreutil, najlepiej coś takiego:

chance 10 && do_stuff

gdzie do_stuffjest wykonywany tylko 1 na 10 razy? Wiem, że mógłbym napisać scenariusz, ale wydaje się to dość proste i zastanawiałem się, czy istnieje określony sposób.

retnikt
źródło
1
Jest to dość dobry wskaźnik, że twój skrypt prawdopodobnie wymyka się spod kontroli, aby bash mógł nadal być rozsądnym wyborem. Powinieneś rozważyć pełnoprawny język programowania, być może język skryptowy, taki jak Python lub Ruby.
Alexander - Przywróć Monikę
@Alexander to nie jest tak naprawdę skrypt, tylko jedna linia. Używam go do zadania cron, aby od czasu do czasu powiadamiać mnie losowo jako przypomnienie o zrobieniu czegoś
retnikt

Odpowiedzi:

40

W ksh, Bash, Zsh, Yash lub BusyBox sh:

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

RANDOMSpecjalny zmienny Korn, Bash, Yash, Z i muszli BusyBox produkuje dziesiętną wartość całkowitą pseudolosowych między 0 a 32767 każdym razem jest to oceniane, więc powyższe daje (prawie) do jednego z dziesięciu przypadek.

Możesz użyć tego do utworzenia funkcji, która zachowuje się tak, jak opisano w pytaniu, przynajmniej w Bash:

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

Zapomnienie podania argumentu lub podanie niepoprawnego argumentu da wynik 1, więc chance && do_stuffnigdy nie będzie do_stuff.

Wykorzystuje to ogólną formułę dla „1 in n ” przy użyciu $RANDOM, co [[ $RANDOM -lt $((32767 / n + 1)) ]]daje szansę (⎣32767 / n ⎦ + 1) na 32768. Wartości, nktóre nie są czynnikami 32768, wprowadzają błąd systematyczny z powodu nierównomiernego podziału w zakresie możliwych wartości.

Stephen Kitt
źródło
(1/2) Po ponownym odwiedzeniu tego problemu: intuicyjne jest ustalenie górnego limitu liczby części, nie można na przykład mieć 40 000 części. W rzeczywistości rzeczywisty limit jest znacznie mniejszy. Problem polega na tym, że podział na liczby całkowite jest jedynie przybliżeniem wielkości każdej części. Wymagane jest, aby dwie kolejne części powodowały, że różnica limitu $((32767/parts+1))była większa niż 1, lub grozi nam zwiększenie liczby części w 1, podczas gdy wynik podziału (a zatem i limitu) będzie taki sam. (cd.)
Izaak
(2/2) (cd.) To będzie stanowiło więcej liczb, niż są faktycznie dostępne. Wzór na to jest taki (32767/n-32767/(n+1))>=1, że rozwiązywanie dla n daje limit na ~ 181,5. W rzeczywistości liczba części może przekroczyć 194 bez problemu. Ale przy 195 częściach wynikowy limit wynosi 151, taki sam wynik jak w przypadku 194 części. Jest to niespójne i należy tego unikać. Krótko mówiąc, górna granica liczby części (n) powinna wynosić 194. Można wykonać testy limitów:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Niestandardowe rozwiązanie:

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

Sprawdź, czy ostatnia cyfra bieżącego czasu w nanosekundach to 1!

stackzebra
źródło
To cudownie.
Eric Duminil,
2
Musisz upewnić się, że połączenie [ $(date +%1N) == 1 ] && do_stuffnie pojawia się w regularnych odstępach czasu, w przeciwnym razie losowość zostanie zepsuta. Pomyśl while true; do [ $(date +1%N) == 1 ] && sleep 1; doneo abstrakcyjnym kontrprzykładzie. Niemniej jednak pomysł gry z nanosekundami jest naprawdę dobry, myślę, że go
użyję
3
@XavierStuvw Myślę, że miałbyś rację, gdyby kod sprawdzał sekundy. Ale nanosekundy? Powinno to wyglądać naprawdę losowo.
Eric Duminil,
9
@EricDuminil: Niestety: 1) Zegar systemowy nie jest umownie zobowiązany do zapewnienia rzeczywistej rozdzielczości nanosekundowej (może zaokrąglić do najbliższej odległości clock_getres()), 2) Harmonogramowi nie zabrania się, na przykład, rozpoczynania szczeliny czasowej na granicy 100 nanosekund (który wprowadziłby błąd, nawet jeśli zegar ma rację), 3) Obsługiwanym sposobem uzyskiwania losowych wartości jest (zwykle) odczyt /dev/urandomlub kontrola wartości $RANDOM.
Kevin
1
@Kevin: Bardzo dziękuję za komentarz.
Eric Duminil,
21

Alternatywą dla użycia $RANDOMjest shufpolecenie:

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

wykona robotę. Przydatny również do losowego wybierania linii z pliku, np. dla listy odtwarzania muzyki.

seumasmac
źródło
1
Nie znałem tego polecenia. Bardzo dobrze!
Eisenknurr
12

Poprawianie pierwszej odpowiedzi i uczynienie bardziej oczywistym tego, co próbujesz osiągnąć:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
źródło
1
$RANDOM % 10będzie miał stronniczość, chyba że da $RANDOMdokładnie 10 różnych wartości (co zwykle nie dzieje się w komputerze binarnym)
phuclv
1
@phuclv Tak, będzie miał takie samo nastawienie jak "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Tak, błąd systematyczny jest identyczny, 0,100006104 zamiast 0,1 dla 1 na 10 (przy sprawdzaniu z 0).
Stephen Kitt
1

Nie jestem pewien, czy chcesz losowości czy okresowości ... Dla okresowości:

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

Możesz go połączyć z powyższą sztuczką „RANDOM”, aby uzyskać coś bardziej chaotycznego, na przykład:

dla mnie w seq 1 1000 $RANDOM; wykonaj echo $ i; gotowe

HTH :-)

Dr_ST
źródło