Как запустить команду 1 из N раз в Bash

15

Я хочу, чтобы команда запускалась случайным образом, скажем, 1 из 10 раз. Есть ли встроенный или GNU coreutil для этого, в идеале что-то вроде:

chance 10 && do_stuff

где do_stuffвыполняется только 1 в 10 раз? Я знаю, что мог бы написать сценарий, но это кажется довольно простой вещью, и мне было интересно, есть ли определенный путь.

retnikt
источник
1
Это довольно хороший показатель того, что ваш скрипт, вероятно, выходит из-под контроля, чтобы bash оставался разумным выбором. Вы должны рассмотреть более полноценный язык программирования, возможно, язык сценариев, такой как Python или Ruby.
Александр - Восстановить Монику
@ Александр, это даже не сценарий, а всего лишь одна строка. Я использую это в cron-задании, чтобы время от времени уведомлять меня случайным образом, как напоминание о необходимости что-то сделать
retnikt

Ответы:

40

В kshBash, Zsh, Yash или BusyBox sh:

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

RANDOMСпециальная переменная Корн, Bash, Яш, Z и BusyBox оболочек производит псевдослучайное десятичное целое число в диапазоне от 0 до 32767 каждый раз , когда она оценена, так что выше , дает (близко к) на один из десяти случайно.

Вы можете использовать это для создания функции, которая ведет себя так, как описано в вашем вопросе, по крайней мере, в Bash:

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

Если вы забудете предоставить аргумент или предоставите неверный аргумент, результат будет равен 1, поэтому chance && do_stuffникогда не будет do_stuff.

При этом используется общая формула для «1 в n » с использованием $RANDOM, которая [[ $RANDOM -lt $((32767 / n + 1)) ]]дает (⎣32767 / n ⎦ + 1) с вероятностью 32768. Значения, nкоторые не являются факторами 32768, вносят смещение из-за неравномерного разделения диапазона возможных значений.

Стивен Китт
источник
(1/2) При повторном посещении этого вопроса: интуитивно понятно, что должен быть верхний предел количества деталей, например, невозможно иметь 40 000 деталей. На самом деле реальный лимит гораздо меньше. Проблема в том, что целочисленное деление - это всего лишь приблизительная оценка того, насколько большой может быть каждая часть. Требуется, чтобы две последовательные части приводили к тому, что разница в пределе была $((32767/parts+1))бы больше 1, или мы рискуем увеличить число частей в 1, в то время как результат деления (и, следовательно, предел) был бы одинаковым. (продолжение)
Исаак
(2/2) (продолжение) Это будет учитывать больше чисел, чем фактически доступно. Формула для этого есть (32767/n-32767/(n+1))>=1решение для n, которое дает предел в ~ 181,5. На самом деле количество деталей может доходить до 194 без проблем. Но в 195 частях результирующий предел составляет 151, тот же результат, что и в 194 частях. Это несовместимо, и его следует избегать. Короче говоря, верхний предел для количества частей (n) должен быть 194. Вы можете сделать предельные тесты:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Исаак
25

Нестандартное решение:

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

Проверьте, является ли последняя цифра текущего времени в наносекундах 1!

stackzebra
источник
Это потрясающе.
Эрик
2
Вы должны убедиться, что вызов [ $(date +%1N) == 1 ] && do_stuffне происходит через равные промежутки времени, иначе случайность будет испорчена. Думайте об этом while true; do [ $(date +1%N) == 1 ] && sleep 1; doneкак абстрактный контрпример. Тем не менее, идея игры с наносекундами действительно хороша, я думаю, что буду ее использовать, поэтому +1
XavierStuvw
3
@XavierStuvw Я думаю, у вас есть смысл, если код проверяет секунды. Но наносекунды? Это должно казаться действительно случайным.
Эрик
9
@EricDuminil: К сожалению: 1) Системные часы по контракту не обязаны предоставлять фактическое разрешение наносекунды (оно может округляться до ближайшего clock_getres()), 2) Планировщику не запрещено, например, всегда запускать временной интервал на границе 100 наносекунд (что привело бы к смещению, даже если часы верны), 3) Поддерживаемый способ получения случайных значений - это (обычно) чтение /dev/urandomили проверка значения $RANDOM.
Кевин
1
@Kevin: Большое спасибо за комментарий.
Эрик
21

Альтернативой использованию $RANDOMявляется shufкоманда:

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

сделаю работу. Также полезно для случайного выбора строк из файла, например. для музыкального плейлиста.

seumasmac
источник
1
Не знал эту команду. Очень хорошо!
Eisenknurr
12

Улучшение первого ответа и сделать намного более очевидным, что вы пытаетесь достичь:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
источник
1
$RANDOM % 10будет иметь смещение, если только он не произведет $RANDOMровно кратное 10 различным значениям (что обычно не происходит в двоичном компьютере)
phuclv
1
@phuclv Да, он будет иметь такой же уклон, как и "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Да, смещение идентично, 0,100006104 вместо 0,1 для 1-в-10 (при сравнении с 0).
Стивен Китт
1

Я не уверен, хотите ли вы случайности или периодичности ... Для периодичности:

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

Вы можете смешать его с уловкой «$ RANDOM» выше, чтобы создать что-то более хаотичное, например:

ибо я в seq 1 1000 $RANDOM; сделать эхо $ я; сделано

HTH :-)

Dr_ST
источник