Como gerar uma string aleatória no Ruby


747

No momento, estou gerando uma sequência maiúscula pseudo-aleatória de 8 caracteres para "A" .. "Z":

value = ""; 8.times{value  << (65 + rand(25)).chr}

mas não parece limpo e não pode ser passado como argumento, pois não é uma declaração única. Para obter uma sequência de maiúsculas e minúsculas "a" .. "z" mais "A" .. "Z", mudei para:

value = ""; 8.times{value << ((rand(2)==1?65:97) + rand(25)).chr}

mas parece lixo.

Alguém tem um método melhor?


Eu não entendo por que você se importa que "uma vez que não é uma única afirmação, não pode ser aprovada como argumento". Por que não torná-lo um método utilitário ou auxiliar?
18747 David J.Julho

1
Suponha que exista um método para redefinir a senha de um usuário e que tenha um argumento para a nova senha. Eu gostaria de passar uma string aleatória, no código acima eu preciso de uma variável tmp, enquanto nos exemplos de instrução única abaixo eu posso fazer a coisa toda como um liner. Certamente, um método utilitário pode ser bom a longo prazo, especialmente se eu precisar de algo semelhante aqui e ali, mas às vezes você apenas o quer no lugar, uma vez, pronto.
26412 Jeff

Não, você não precisa usar uma variável temporária. Tente isto: reset_user_password!(random_string)wheredef random_string; SecureRandom.urlsafe_base64(20) end
David J.

8 letras é uma senha vergonhosamente fraca. Dado o md5sum, um PC moderno pode recuperar a senha em 30 segundos . Que tal algo maissecurerandom.urlsafe_base64
Coronel Panic

1
Por que isso tem tantas respostas? Não que não seja uma pergunta útil, mas estou curioso para saber como isso atraiu a atenção.
16604 Lou Lou

Respostas:


970
(0...8).map { (65 + rand(26)).chr }.join

Eu gasto muito tempo jogando golfe.

(0...50).map { ('a'..'z').to_a[rand(26)] }.join

E um último que é ainda mais confuso, mas mais flexível e gasta menos ciclos:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join

205
34 personagens e super rápido: ('a'..'z').to_a.shuffle[0,8].join. Observe que você precisará do Ruby> = 1.9 para shuffle.
FNY

20
É preferível alavancar as bibliotecas existentes, a menos que você tenha um driver para rodar por conta própria. Veja SecureRandomcomo um exemplo, nas outras respostas.
26712 David J.

28
@faraz, seu método não é funcionalmente o mesmo, não é aleatório com a substituição.
michaeltwofish

35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinpara gerar uma sequência aleatória com letras e números.
Robin

31
randé determinístico e previsível. Não use isso para gerar senhas! Use uma das SecureRandomsoluções.
lápis

800

Por que não usar o SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

# outputs: 5b5cd0da3121fc53b4bc84d0c8af2e81 (i.e. 32 chars of 0..9, a..f)

O SecureRandom também possui métodos para:

  • base64
  • random_bytes
  • número aleatório

consulte: http://ruby-doc.org/stdlib-1.9.2/libdoc/securerandom/rdoc/SecureRandom.html


11
base64 faria, mas não hex como no seu exemplo
Jeff Dickey

67
By the way, que é parte do stdlib em 1,9 e 1,8 recentes versões, então pode-se apenas require 'securerandom'para obter esta arrumado SecureRandomajudante :)
J -_- L

21
O BTW SecureRandom foi removido do ActiveSupport na versão 3.2. No changelog: "Removido o ActiveSupport :: SecureRandom em favor do SecureRandom da biblioteca padrão".
Marc

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")irá gerar uma sequência com 0-9a-z (36 caracteres) que SEMPRE terá 12 caracteres. Mude 12 para o tamanho que desejar. Infelizmente, não há como usar o AZ Integer#to_s.
Gerry Shaw

1
@ stringo0 que está errado. Se você quiser passar +fGH1por um URL, basta codificá-lo como qualquer valor que estiver passando por um URL: %2BfGH1
nzifnab

247

Eu uso isso para gerar seqüências amigáveis ​​de URL aleatórias com um comprimento máximo garantido:

rand(36**length).to_s(36)

Ele gera seqüências aleatórias de az minúsculas e 0-9. Não é muito personalizável, mas é curto e limpo.


4
+1 para a versão mais curta (que não chama binários externos ^^). Se a sequência aleatória não é voltada para o público, às vezes eu até uso rand.to_s; feio, mas funciona.
quer

12
Esta é uma ótima solução (e rápida também), mas ocasionalmente produzirá uma string abaixo do length comprimento, aproximadamente uma vez em ~ 40
Brian E

7
E @ Brian isso iria garantir os dígitos desejados: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (comprimento-1) convertido na base 36 é 10 ** (comprimento-1), que é o menor valor que tem o comprimento do dígito desejado.
Eric Hu

11
Aqui está a versão que sempre produz tokens do tamanho desejado:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon

3
Este cospe um erro para mim em Rails 4 e Ruby 2.1.1: NameError: undefined local variable or method comprimento' para principal: Object`
kakubei

175

Esta solução gera uma cadeia de caracteres facilmente legíveis para códigos de ativação; Não queria que as pessoas confundissem 8 com B, 1 com I, 0 com O, L com 1, etc.

# Generates a random string from a set of easily readable characters
def generate_activation_code(size = 6)
  charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z}
  (0...size).map{ charset.to_a[rand(charset.size)] }.join
end

3
'U' é ambíguo ou é um erro de digitação?
Gtd

10
@gtd - Sim. U e V são ambigvovs.
colinm

1
@colinm V está lá, no entanto.
Gtd

8
Para estar seguro, você também gostaria de usar em SecureRandom.random_number(charset.size)vez derand(charset.size)
gtd 10/10

1
Eu estava apenas tentando melhorar isso, adicionando letras minúsculas e / ou alguns caracteres especiais (shift + num), e obtive as seguintes listas: %w{ A C D E F G H J K L M N P Q R T W X Y Z 2 3 4 6 7 9 ! @ # $ % ^ & * +}e %w{ A D E F G H J L N Q R T Y a d e f h n r y 2 3 4 7 ! @ # $ % ^ & * }(a primeira lista não inclui letras minúsculas, mas é mais longa) deu certo.
dkniffin

130

Outros mencionaram algo semelhante, mas isso usa a função segura de URL.

require 'securerandom'
p SecureRandom.urlsafe_base64(5) #=> "UtM7aa8"
p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="

O resultado pode conter AZ, az, 0-9, "-" e "_". "=" Também é usado se o preenchimento for verdadeiro.


Era exatamente isso que eu estava procurando. Obrigado!
Dan Williams

69

Desde o Ruby 2.5, é realmente fácil com SecureRandom.alphanumeric:

len = 8
SecureRandom.alphanumeric(len)
=> "larHSsgL"

Ele gera seqüências aleatórias contendo AZ, az e 0-9 e, portanto, deve ser aplicável na maioria dos casos de uso. E eles são gerados aleatoriamente seguros, o que também pode ser um benefício.


Esta é uma referência para compará-la com a solução com mais votos positivos:

require 'benchmark'
require 'securerandom'

len = 10
n = 100_000

Benchmark.bm(12) do |x|
  x.report('SecureRandom') { n.times { SecureRandom.alphanumeric(len) } }
  x.report('rand') do
    o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
    n.times { (0...len).map { o[rand(o.length)] }.join }
  end
end

                   user     system      total        real
SecureRandom   0.429442   0.002746   0.432188 (  0.432705)
rand           0.306650   0.000716   0.307366 (  0.307745)

Portanto, a randsolução leva apenas cerca de 3/4 do tempo de SecureRandom. Isso pode importar se você gerar muitas strings, mas se você criar algumas strings aleatórias de vez em quando, eu sempre utilizarei uma implementação mais segura, pois também é mais fácil chamar e mais explícito.


48
[*('A'..'Z')].sample(8).join

Gere uma sequência aleatória de 8 letras (por exemplo, NVAYXHGR)

([*('A'..'Z'),*('0'..'9')]-%w(0 1 I O)).sample(8).join

Gere uma sequência aleatória de 8 caracteres (por exemplo, 3PH4SWF2), excluindo 0/1 / I / O. Ruby 1.9


4
O único problema é que cada caractere no resultado é único. Limita os valores possíveis.
Tybro0103

1
Se essa solicitação de recurso for aprovada, o Ruby 1.9.x poderá acabar com #sample para amostragem sem substituição e #choice para amostragem com substituição.
David J.

Isso é um erro, acho que você precisa ... [*("A".."Z")]'; ((qoutes não únicos))
jayunit100

Você pode me dizer como eu posso fazer stubisso rspec?
Sinscary

31

Não me lembro onde encontrei isso, mas parece o melhor e o menos intensivo para mim:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end


Não há 0 e 1 faltando?
sunnyrjuneja

Parece que poderia ser onde eu o encontrei.
Travis Reeder

E sim, parece que 0 e 1 estão faltando.
Travis Reeder

4
O 0e 1e Oe Iestavam intencionalmente ausentes porque esses caracteres são ambíguos. Se esse tipo de código estiver sendo usado para gerar um conjunto de caracteres que um usuário precisa copiar, é melhor excluir caracteres que podem ser difíceis de distinguir fora de contexto.
Andrew

28
require 'securerandom'
SecureRandom.urlsafe_base64(9)

SecureRandom é uma excelente biblioteca. Eu não sabia que estava lá. Obrigado.
Dogweather

alternativa skool antiga (e mais feia):Random.new.bytes(9)
atrasada 20/09/12

4
btw, urlsafe_base64 retorna uma string com cerca de 4/3 do comprimento indicado. Para obter uma string com exatamente n caracteres, tenten=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
atrasado 20/09/12

25

Se você deseja uma sequência de comprimento especificado, use:

require 'securerandom'
randomstring = SecureRandom.hex(n)

Irá gerar uma sequência aleatória de comprimento 2ncontendo 0-9ea-f


Ele não gera uma sequência de comprimento n, na verdade gera uma sequência com 4/3 de n.
Omar Ali

@ OmarAli você está errado. Conforme a documentação do Ruby, no caso de .hex2n. Documentação: SecureRandom.hex gera uma sequência hexadecimal aleatória. O argumento n especifica o comprimento, em bytes, do número aleatório a ser gerado. O comprimento da sequência hexadecimal resultante é duas vezes de n .
Rihards


11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]

Interessante, mas um pouco mais caro computacionalmente
Jeff

Também não há capacidade para escopo limitado de caracteres.
21710 Kent Kentric

3
Lembre-se de que um resumo hexadecimal retorna apenas 0-9 e af caracteres.
webmat 18/09/08

11

Aqui está um código simples de uma linha para sequência aleatória com comprimento 8:

 random_string = ('0'..'z').to_a.shuffle.first(8).join

Você também pode usá-lo para senha aleatória com tamanho 8:

random_password = ('0'..'z').to_a.shuffle.first(8).join

2
Você não deve usar isso para gerar uma senha para si mesmo, pois esse método nunca repetirá um caractere. Portanto, você está usando apenas P(36, 8) / 36^8 = 0.4o espaço de caracteres possível para 8 caracteres (~ 2x mais fácil para a força bruta) ou P(36, 25) / 36^25 = 0.00001o espaço possível para 25 caracteres (~ 100.000x mais fácil para a força bruta).
Glacials 07/07

10

Ruby 1.9+:

ALPHABET = ('a'..'z').to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

10.times.map { ALPHABET.sample }.join
#=> "stkbssowre"

# or

10.times.inject('') { |s| s + ALPHABET.sample }
#=> "fdgvacnxhc"

1
A mapsolução é muito boa!
Misha Moroshko

Você pode pedir #samplequantos elementos deseja. Por exemplo ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp

Isso está realmente errado. sample(10)fornece 10 amostras únicas. Mas você deseja permitir que eles se repitam. Mas eu usaria Array.new(10).mappara performance
Saša Zejnilović

Eu queria alfanumérico com letras maiúsculas e minúsculas. Também mudei para usar Array.newe sua sintaxe de bloco. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
precisa saber é o seguinte

8

Esteja ciente: randé previsível para um invasor e, portanto, provavelmente inseguro. Você definitivamente deve usar o SecureRandom se isso for para gerar senhas. Eu uso algo como isto:

length = 10
characters = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a

password = SecureRandom.random_bytes(length).each_char.map do |char|
  characters[(char.ord % characters.length)]
end.join

Esta é provavelmente a solução "mais" segura. O SecureRandom tenta usar as APIs de segurança subjacentes fornecidas pelo sistema operacional. Se você possui o OpenSSL, ele o usará; se você estiver no Windows, será a melhor opção. Eu gosto especialmente desta solução porque permite especificar um conjunto de caracteres para uso. Embora não funcione se o seu conjunto de caracteres for maior que o valor máximo de um byte: 255. Recomendo visualizar o código fonte do SecureRandom no doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc /…
Breedly 16/02

8

Aqui está um código simples para senha aleatória com comprimento 8:

rand_password=('0'..'z').to_a.shuffle.first(8).join

7

Outro método que eu gosto de usar:

 rand(2**256).to_s(36)[0..7]

Adicione ljustse você é realmente paranóico sobre o comprimento correto da string:

 rand(2**256).to_s(36).ljust(8,'a')[0..7]

Ainda melhor para agarrar a parte menos significativa do número aleatório, usando o lado direito da string: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff

6
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Algo do Devise


Por que está substituindo caracteres de cadeia usando .tr('+/=lIO0', 'pqrsxyz')?
Miguelcobain # 9/13

Os caracteres especiais porque eles não são seguros para URL. E l / I ou O / 0 porque são muito fáceis de confundir se você usar a técnica para gerar senhas de usuário legíveis.
ToniTornado 5/11

Essa função tem um certo viés em relação a certos caracteres. Também para outros comprimentos (por exemplo, 16), o último caractere não será aleatório. Aqui está uma maneira de evitar isso. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman

5

Penso que este é um bom equilíbrio entre concisão, clareza e facilidade de modificação.

characters = ('a'..'z').to_a + ('A'..'Z').to_a
# Prior to 1.9, use .choice, not .sample
(0..8).map{characters.sample}.join

Modificado facilmente

Por exemplo, incluindo dígitos:

characters = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a

Hexadecimal maiúsculo:

characters = ('A'..'F').to_a + (0..9).to_a

Para uma variedade verdadeiramente impressionante de caracteres:

characters = (32..126).to_a.pack('U*').chars.to_a

1
eu recomendaria isso para usar apenas letras maiúsculas + números, também remover o charset "confuso" = (1..9) .to_a.concat (('A' .. 'Z'). to_a) .reject {| a | [0, 1, 'O', 'I']. Include? (A)} (0 ... tamanho) .map {charset [rand (charset.size)]} .join
lustroso

5

Apenas adicionando meus centavos aqui ...

def random_string(length = 8)
  rand(32**length).to_s(32)
end

3
Nota: isso nem sempre retorna uma string exatamente + comprimento + comprimento - pode ser mais curta. Depende do número retornado porrand
tardio

5

Você pode usar String#randomas Facets of Ruby Gem facets.

Basicamente, faz isso:

class String
  def self.random(len=32, character_set = ["A".."Z", "a".."z", "0".."9"])
    characters = character_set.map { |i| i.to_a }.flatten
    characters_len = characters.length
    (0...len).map{ characters[rand(characters_len)] }.join
  end
end

4

O meu favorito é (:A..:Z).to_a.shuffle[0,8].join. Observe que o shuffle requer Ruby> 1.9.


4

Essa solução precisa de dependência externa, mas parece mais bonita que outra.

  1. Instalar gem faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

4

Dado:

chars = [*('a'..'z'),*('0'..'9')].flatten

Expressão única, pode ser transmitida como argumento, permite caracteres duplicados:

Array.new(len) { chars.sample }.join

3

Eu gosto da resposta do Radar melhor, até agora, eu acho. Eu mexeria um pouco assim:

CHARS = ('a'..'z').to_a + ('A'..'Z').to_a
def rand_string(length=8)
  s=''
  length.times{ s << CHARS[rand(CHARS.length)] }
  s
end

Por que não usar (CHARS*length).sample(length).join?
Niels 20/06

@niels Essa sugestão geraria uma sequência ponderada , em favor de caracteres não repetidos. Por exemplo, se CHARS=['a','b'], em seguida, o seu método geraria "aa"ou "bb"apenas 33% do tempo, mas "ab"ou "ba"67% do tempo. Talvez isso não seja um problema, mas vale a pena ter em mente!
Tom Lord

bom ponto, @TomLord, eu acho que eu realmente não perceber que quando eu postei essa sugestão (embora eu deva admitir que eu não me lembro de postagem que em tudo: D)
Niels

3
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }

3

Meus 2 centavos:

  def token(length=16)
    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
    (0..length).map {chars.sample}.join
  end


3

2 soluções para uma sequência aleatória composta por 3 intervalos:

(('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).sample(8).join

([*(48..57),*(65..90),*(97..122)]).sample(8).collect(&:chr)*""

Um personagem de cada intervalo.

E se você precisar de pelo menos um caractere de cada intervalo, como criar uma senha aleatória com uma letra maiúscula, uma letra minúscula e um dígito, faça algo assim:

( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
#=> "Kc5zOGtM0H796QgPp8u2Sxo1"

E se você deseja impor um certo número de cada faixa, você poderia fazer algo como isto:( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) + [ "%", "!", "*" ].sample(8) ).shuffle.join #=> "Kc5zOGtM0*H796QgPp%!8u2Sxo1"
Joshua Pinter

3

Eu estava fazendo algo assim recentemente para gerar uma sequência aleatória de 8 bytes de 62 caracteres. Os caracteres eram 0-9, az, AZ. Eu tinha uma matriz deles como estava repetindo 8 vezes e escolhendo um valor aleatório fora da matriz. Isso foi dentro de um aplicativo Rails.

str = ''
8.times {|i| str << ARRAY_OF_POSSIBLE_VALUES[rand(SIZE_OF_ARRAY_OF_POSSIBLE_VALUES)] }

O estranho é que consegui um bom número de duplicatas. Agora, aleatoriamente, isso quase nunca deve acontecer. 62 ^ 8 é enorme, mas em cerca de 1200 códigos no db eu tinha um bom número de duplicatas. Eu os notei acontecendo nos limites das horas um do outro. Em outras palavras, eu posso ver um duplo em 12:12:23 e 2:12:22 ou algo assim ... não tenho certeza se o tempo é o problema ou não.

Esse código estava na criação anterior de um objeto ActiveRecord. Antes da criação do registro, esse código seria executado e geraria o código 'exclusivo'. As entradas no banco de dados sempre foram produzidas com confiabilidade, mas o código ( strna linha acima) estava sendo duplicado com muita frequência.

Criei um script para executar 100000 iterações dessa linha acima com um pequeno atraso, de modo que levaria de 3 a 4 horas na esperança de ver algum tipo de padrão de repetição a cada hora, mas não vi nada. Não tenho idéia do por que isso estava acontecendo no meu aplicativo Rails.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.