Como executar um comando 1 de N vezes no Bash

15

Eu quero uma maneira de executar um comando aleatoriamente, digamos 1 em cada 10 vezes. Existe um coreutil interno ou GNU para fazer isso, idealmente algo como:

chance 10 && do_stuff

onde do_stuffé executado apenas 1 em 10 vezes? Eu sei que poderia escrever um script, mas parece uma coisa bastante simples e fiquei pensando se existe uma maneira definida.

retnikt
fonte
1
Esse é um indicador bastante bom de que seu script provavelmente está ficando fora de controle para que o bash continue sendo uma escolha razoável. Você deve considerar uma linguagem de programação mais completa, talvez uma linguagem de script como Python ou Ruby.
Alexander - Restabelecer Monica
@ Alexander, isso nem é realmente um script, apenas uma linha. Eu estou usando-o em um cron-job para notificar-me aleatoriamente a cada momento e, em seguida, como um lembrete para fazer algo
retnikt

Respostas:

40

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

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

A RANDOMvariável especial dos shells Korn, Bash, Yash, Z e BusyBox produz um valor inteiro decimal pseudo-aleatório entre 0 e 32767 toda vez que é avaliada; portanto, o acima fornece (próximo a) uma chance em dez.

Você pode usar isso para produzir uma função que se comporte conforme descrito na sua pergunta, pelo menos no Bash:

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

Esquecer de fornecer um argumento, ou fornecer um argumento inválido, produzirá o resultado 1, portanto chance && do_stuffnunca o será do_stuff.

Esta utiliza a fórmula geral para a “1 em n ” utilizando $RANDOM, o que é [[ $RANDOM -lt $((32767 / n + 1)) ]], dando um (⎣32767 / n ⎦ + 1) em 32768 acaso. Valores nque não são fatores de 32768 introduzem um viés por causa da divisão desigual no intervalo de valores possíveis.

Stephen Kitt
fonte
(1/2) Ao visitar novamente esta questão: É intuitivo que exista um limite superior no número de peças, não é possível ter 40.000 peças, por exemplo. Na verdade, o limite real é bem menor. A questão é que a divisão inteira é apenas uma aproximação do tamanho de cada parte. É necessário que duas partes consecutivas resultem em uma diferença do limite $((32767/parts+1))maior que 1 ou corremos o risco de aumentar o número de partes em 1, enquanto o resultado da divisão (e, portanto, o limite) é o mesmo. (cont ..)
Isaac
(2/2) (continuação) Isso representará mais números do que o que está realmente disponível. A fórmula para isso é (32767/n-32767/(n+1))>=1resolver n que fornece um limite em ~ 181,5. De fato, o número de peças pode chegar a 194 sem problemas. Mas em 195 partes, o limite resultante é 151, o mesmo resultado que em 194 partes. Isso é incongruente e deve ser evitado. Em resumo, o limite superior para o número de partes (n) deve ser 194. Você pode fazer os testes de limite:[[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1
Isaac
25

Solução não padrão:

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

Verifique se o último dígito da hora atual em nanossegundos é 1!

stackzebra
fonte
Fantástico.
Eric Duminil
2
Você deve garantir que a chamada [ $(date +%1N) == 1 ] && do_stuffnão ocorra em intervalos regulares, caso contrário, a aleatoriedade será estragada. Pense while true; do [ $(date +1%N) == 1 ] && sleep 1; donecomo um contra-exemplo abstrato. No entanto, a idéia de jogar com os nanossegundos é realmente bom, eu acho que vai usá-lo, daí +1
XavierStuvw
3
@XavierStuvw Eu acho que você teria razão se o código estivesse verificando segundos. Mas nanossegundos? Deve parecer realmente aleatório.
Eric Duminil
9
@EricDuminil: Infelizmente: 1) O relógio do sistema não é contratualmente obrigado a fornecer uma resolução real de nanossegundos (pode arredondar para a mais próxima clock_getres()), 2) O agendador não é proibido de, por exemplo, sempre iniciar uma divisão de tempo em um limite de 100 nanossegundos (o que introduziria viés mesmo que o relógio estivesse correto), 3) A maneira suportada de obter valores aleatórios é (geralmente) lendo /dev/urandomou inspecionando o valor de $RANDOM.
Kevin
1
@ Kevin: Muito obrigado pelo comentário.
Eric Duminil
21

Uma alternativa ao uso $RANDOMé o shufcomando:

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

fará o trabalho. Também é útil para selecionar linhas aleatoriamente de um arquivo, por exemplo. para uma lista de reprodução de música.

seumasmac
fonte
1
Não conhecia esse comando. Muito agradável!
Eisenknurr
12

Melhorando a primeira resposta e tornando muito mais óbvio o que você está tentando alcançar:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
Eisenknurr
fonte
1
$RANDOM % 10terá um viés, a menos que o $RANDOMproduz exatamente um múltiplo de 10 valores diferentes (o que geralmente não acontece em um computador binário)
phuclv
1
@phuclv Sim, ele terá o mesmo viés que "$RANDOM" -lt $((32767 / n + 1)).
Eisenknurr
Sim, o viés é idêntico, 0.100006104 em vez de 0,1 para 1 em 10 (ao verificar contra 0).
Stephen Kitt
1

Não tenho certeza se você deseja aleatoriedade ou periodicidade ... Para periodicidade:

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

Você pode misturá-lo com o truque "$ RANDOM" acima para produzir algo mais caótico, por exemplo:

para eu dentro seq 1 1000 $RANDOM; echo $ i; done

HTH :-)

Dr_ST
fonte