Как сгенерировать случайную строку в Ruby

747

В настоящее время я генерирую 8-символьную псевдослучайную строку в верхнем регистре для "A" .. "Z":

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

но он не выглядит чистым и не может быть передан в качестве аргумента, поскольку это не одно утверждение. Чтобы получить строку со смешанным регистром "a" .. "z" плюс "A" .. "Z", я изменил ее на:

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

но это похоже на мусор.

У кого-нибудь есть лучший метод?

Jeff
источник
Я не понимаю, почему вас это волнует, «поскольку это не одно утверждение, оно не может быть передано в качестве аргумента». Почему бы просто не сделать это утилитарным или вспомогательным методом?
Дэвид Дж.
1
Предположим, что существует метод сброса пароля пользователя, и у него есть аргумент для нового пароля. Я хотел бы передать случайную строку, в приведенном выше коде мне нужна переменная tmp, в то время как в приведенных ниже примерах операторов я могу сделать все это в одну строку. Конечно, в конечном итоге полезный метод может быть полезен, особенно если мне нужно подобное здесь и там, но иногда вы просто хотите, чтобы это было на месте, однажды, сделанное.
Джефф
Нет, вам не нужно использовать временную переменную. Попробуйте это: reset_user_password!(random_string)гдеdef random_string; SecureRandom.urlsafe_base64(20) end
Дэвид Дж.
8 букв - позорно слабый пароль. С учетом md5sum современный ПК может восстановить пароль за 30 секунд . Как насчет чего-то большегоsecurerandom.urlsafe_base64
полковник Паник
1
Почему на это так много ответов? Не то чтобы это не был полезный вопрос, но мне любопытно, как это привлекло внимание.
Лу

Ответы:

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

Я провожу слишком много времени в гольфе.

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

И последний, который еще более запутанный, но более гибкий и тратит меньше циклов:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join
Кент Фредрик
источник
205
34 персонажа и молниеносно ('a'..'z').to_a.shuffle[0,8].join. Обратите внимание, что вам понадобится Ruby> = 1,9 shuffle.
Fny
20
Использование существующих библиотек предпочтительнее, если только у вас нет драйвера, на который можно катиться. Смотрите SecureRandomкак один пример, в других ответах.
Дэвид Дж.
28
@faraz Ваш метод не является функционально одинаковым, он не случайный с заменой.
michaeltwofish
35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinгенерировать случайную строку с буквами и цифрами.
Робин
31
randявляется детерминированным и предсказуемым. Не используйте это для генерации паролей! SecureRandomВместо этого используйте одно из решений.
карандаш
800

Почему бы не использовать SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom также имеет методы для:

  • base64
  • random_bytes
  • случайное число

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

christopherstyles
источник
11
Base64 будет, но не в шестнадцатеричном виде, как в его примере
Джефф Дики
67
Кстати, это часть stdlib в 1.9 и последних версиях 1.8, так что можно просто require 'securerandom'получить этого аккуратного SecureRandomпомощника :)
J -_- L
21
Кстати, SecureRandom был удален из ActiveSupport в версии 3.2. Из журнала изменений: «Удален ActiveSupport :: SecureRandom в пользу SecureRandom из стандартной библиотеки».
Marc
15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")сгенерирует строку с 0-9a-z (36 символов) длиной ВСЕГДА 12 символов. Измените 12 на любую длину, какую хотите. К сожалению, нет способа просто использовать AZ Integer#to_s.
Джерри Шоу
1
@ stringo0, это неправильно. Если вы хотите пропустить +fGH1URL-адрес, вам просто нужно закодировать его URL-адресом так, как если бы вы ЛЮБИЛИ значение, проходящее через URL-адрес: %2BfGH1
nzifnab
247

Я использую это для генерации случайных URL-дружественных строк с гарантированной максимальной длиной:

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

Он генерирует случайные строки из строчных букв az и 0-9. Это не очень настраиваемый, но он короткий и чистый.

Christoffer Möller
источник
4
+1 для самой короткой версии (которая не вызывает внешние двоичные файлы ^^). Если случайная строка не является общедоступной, я иногда просто использую ее rand.to_s; некрасиво, но работает.
Джо Лисс
12
Это отличное решение (и быстрое тоже), но оно иногда приводит к получению строки в length длину, примерно один раз в ~ 40
Брайан Э.
7
E @ Брайан это гарантировало бы цифры , которые вы хотите: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (длина-1), преобразованная в основание 36, равна 10 ** (длина-1), что является наименьшим значением, имеющим необходимую длину цифры.
Эрик Ху,
11
Вот версия, всегда производящая жетоны желаемой длины:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon
3
Это выдает ошибку для меня в Rails 4 и Ruby 2.1.1: NameError: undefined local variable or method длина 'для main: Object`
kakubei
175

Это решение генерирует строку легко читаемых символов для кодов активации; Я не хотел, чтобы люди путали 8 с B, 1 с I, 0 с O, L с 1 и т. Д.

# 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
ImNotQuiteJack
источник
3
«U» неоднозначно или это опечатка?
gtd
10
@gtd - Да. U и V - амбигвовы.
Колинм
1
@colinm V там, хотя.
gtd
8
Чтобы быть в безопасности, вы также хотели бы использовать SecureRandom.random_number(charset.size)вместоrand(charset.size)
gtd
1
Я просто пытался улучшить это, добавив строчные буквы и / или некоторые специальные символы (shift + num), и получил следующие списки: %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 ! @ # $ % ^ & * +}и %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 ! @ # $ % ^ & * }(первый список не включает строчные буквы, но он длиннее). Интересно, как это Разработаны.
dkniffin
130

Другие упоминали что-то похожее, но при этом используется функция безопасного URL.

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

Результат может содержать AZ, az, 0-9, «-» и «_». «=» Также используется, если отступ равен true.

Travis R
источник
Это именно то, что я искал. Спасибо!
Дэн Уильямс,
71

Начиная с Ruby 2.5, это действительно легко с SecureRandom.alphanumeric:

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

Он генерирует случайные строки, содержащие AZ, az и 0-9, и поэтому должен применяться в большинстве случаев использования. И они генерируются случайным образом, что также может быть полезным.


Это тест для сравнения с решением, имеющим наибольшее количество голосов:

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)

Таким образом, randрешение занимает всего около 3/4 времени SecureRandom. Это может иметь значение, если вы генерируете много строк, но если вы просто время от времени создаете какую-то случайную строку, я всегда выбираю более безопасную реализацию, так как ее также проще вызывать и более явной.

Markus
источник
48
[*('A'..'Z')].sample(8).join

Генерация случайной 8-буквенной строки (например, NVAYXHGR)

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

Генерация случайной 8-символьной строки (например, 3PH4SWF2), исключая 0/1 / I / O. Рубин 1.9

Том Карр
источник
4
Единственная проблема - каждый персонаж в результате уникален. Ограничивает возможные значения.
tybro0103
1
Если этот запрос будет выполнен, Ruby 1.9.x может получить #sample для выборки без замены и #choice для выборки с заменой.
Дэвид Дж.
Это ошибка, я думаю, что вам нужно ... [*("A".."Z")]'; ((не один раз))
jayunit100
Можете ли вы сказать мне, как я могу stubэто rspecпередать.
Синскари
31

Я не могу вспомнить, где я нашел это, но мне кажется, что это лучший и наименее интенсивный процесс:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end
Travis R
источник
7
Возможно, вы нашли это здесь? travisonrails.com/2007/06/07/generate-random-text-with-ruby
смертельно
Нет ли здесь 0 и 1?
sunnyrjuneja
Похоже, что это может быть, где я нашел это.
Трэвис Ридер
И да, похоже, что 0 и 1 отсутствуют.
Трэвис Ридер
4
0И 1и Oи Iнамеренно не хватает , потому что эти персонажи неоднозначны. Если этот вид кода используется для генерации набора символов, который пользователь должен скопировать, лучше исключить символы, которые может быть трудно выделить вне контекста.
Андрей
28
require 'securerandom'
SecureRandom.urlsafe_base64(9)
LENZCOM
источник
SecureRandom - отличная библиотека. Я не знал, что это было там. Спасибо.
Dogweather
старый вариант skool (и более уродливый):Random.new.bytes(9)
tardate
4
Кстати, urlsafe_base64 возвращает строку примерно на 4/3 указанной длины. Чтобы получить строку длиной ровно n символов, попробуйтеn=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardate
25

Если вы хотите строку указанной длины, используйте:

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

Он сгенерирует случайную строку длины, 2nсодержащую 0-9иa-f

Srikanta Mahapatro
источник
Он не генерирует строку длины n, он фактически генерирует строку, которая составляет 4/3 n.
Омар Али
@ OmarAli ты не прав. В соответствии с документацией Ruby в случае .hex2n. Документация: SecureRandom.hex генерирует случайную шестнадцатеричную строку. Аргумент n указывает длину в байтах случайного числа, которое должно быть сгенерировано. Длина получающейся шестнадцатеричной строки в два раза больше n .
Рихардс
11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]
Корен
источник
Интересно, но немного дороже в вычислительном отношении
Джефф
Также нет возможности для ограниченного объема символов.
Кент Фредрик
3
Имейте в виду, что шестнадцатеричный дайджест возвращает только 0-9 и аф символы.
веб-конференция
11

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"
Ragmaanir
источник
1
mapРешение очень приятно!
Миша Морошко
Вы можете спросить, #sampleсколько элементов вы хотите. Например ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp
Это на самом деле неправильно. sample(10)дает вам 10 уникальных образцов. Но вы хотите позволить им повториться. Но я бы использовал Array.new(10).mapдля производительности
Saša Zejnilović
Я хотел буквенно-цифровой с прописными и строчными буквами. Я также переключился на использование Array.newи его синтаксис блока. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow
11

Вот простой строковый код для случайной строки длиной 8:

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

Вы также можете использовать его для произвольного пароля длиной 8:

random_password = ('0'..'z').to_a.shuffle.first(8).join
Awais
источник
2
Вы не должны использовать это для генерации пароля, так как этот метод никогда не будет повторять символ. Поэтому вы используете только P(36, 8) / 36^8 = 0.4возможное пространство символов для 8 символов (~ в 2 раза легче грубой силы) или P(36, 25) / 36^25 = 0.00001из возможного пространства символов для 25 символов (~ 100 000 раз проще для грубой силы).
Glacials
8

Помните: randпредсказуемо для злоумышленника и, следовательно, вероятно, небезопасно. Вы обязательно должны использовать SecureRandom, если это для генерации паролей. Я использую что-то вроде этого:

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
карандаш
источник
Это, пожалуй, самое безопасное решение. SecureRandom пытается использовать базовые API безопасности, предоставляемые операционной системой. Если у вас есть OpenSSL, он будет использовать это, если вы используете Windows, он выберет лучший вариант. Мне особенно нравится это решение, потому что оно позволяет вам указать набор символов для использования. Хотя он не будет работать, если ваш набор символов длиннее максимального значения байта: 255. Я рекомендую просмотреть исходный код для SecureRandom в doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc /…
Breedly
8

Вот один простой код для случайного пароля длиной 8:

rand_password=('0'..'z').to_a.shuffle.first(8).join
Thaha kp
источник
7

Еще один метод, который мне нравится использовать:

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

Добавьте, ljustесли вы действительно параноик по поводу правильной длины строки:

 rand(2**256).to_s(36).ljust(8,'a')[0..7]
user163365
источник
Еще лучше захватить наименее значимую часть случайного числа, используя правую часть строки: rand (2 ** 64) .to_s (36) [- 10,10]
Джефф
6
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Что-то из Devise

Teej
источник
Почему он заменяет строковые символы с помощью .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain
Специальные символы, потому что они не безопасны для URL. И l / I или O / 0, потому что их очень легко перепутать, если вы используете технику для генерации читаемых пользовательских паролей.
ToniTornado
Эта функция имеет некоторую предвзятость к определенным персонажам. Также для других длин (например, 16) последний символ не будет случайным. Вот способ избежать этого. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Шай Коулман
5

Я думаю, что это хороший баланс краткости, ясности и простоты модификации.

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

Легко модифицируется

Например, включая цифры:

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

Прописные шестнадцатеричные:

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

Для действительно впечатляющего множества персонажей:

characters = (32..126).to_a.pack('U*').chars.to_a
Натан Лонг
источник
1
я бы порекомендовал это просто использовать заглавные буквы + цифры, а также убрать "сбивающие с толку" charset = (1..9) .to_a.concat (('A' .. 'Z'). to_a) .reject {| a | [0, 1, 'O', 'I']. Include? (A)} (0 ... size) .map {charset [rand (charset.size)]} .join
блеск
5

Просто добавив мои центы здесь ...

def random_string(length = 8)
  rand(32**length).to_s(32)
end
pduersteler
источник
3
Примечание: это не всегда возвращает строку точно + длина + длина - она ​​может быть короче. Это зависит от числа, возвращенногоrand
tardate
5

Вы можете использовать String#randomиз граней рубинового камня facets.

Это в основном делает это:

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
Tilo
источник
4

Мой любимый это (:A..:Z).to_a.shuffle[0,8].join. Обратите внимание, что для перемешивания требуется Ruby> 1,9.

мистифицировать
источник
4

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

  1. Установить драгоценный камень Faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"
asiniy
источник
4

Данный:

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

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

Array.new(len) { chars.sample }.join
Тим Джеймс
источник
3

Думаю, мне больше всего нравится ответ Радар. Я бы немного подправил так:

CHARS = ('a'..'z').to_a + ('A'..'Z').to_a
def rand_string(length=8)
  s=''
  length.times{ s << CHARS[rand(CHARS.length)] }
  s
end
webmat
источник
Почему бы не использовать (CHARS*length).sample(length).join?
Нильс
@niels Это предложение будет генерировать взвешенную строку в пользу неповторяющихся символов. Например, если CHARS=['a','b']тогда ваш метод будет генерировать "aa"или "bb"только 33% времени, но "ab"или "ba"67% времени. Может быть, это не проблема, но это стоит иметь в виду!
Том Лорд
Хороший вопрос, @TomLord, я думаю, что на самом деле я не осознавал этого, когда я опубликовал это предложение (хотя должен признать, что вообще не помню, чтобы опубликовать это предложение: D)
niels
3
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }
эрик
источник
3

Мои 2 цента:

  def token(length=16)
    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
    (0..length).map {chars.sample}.join
  end
tybro0103
источник
3

Я просто пишу небольшой гем random_tokenдля генерации случайных токенов для большинства случаев использования, наслаждайтесь ~

https://github.com/sibevin/random_token

Сибевин Ван
источник
Отличная жемчужина. Спасибо :)
Мирко Аков
3

2 решения для случайной строки, состоящей из 3 диапазонов:

(('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)*""

Один персонаж от каждого диапазона.

И если вам нужен хотя бы один символ из каждого диапазона, например, для создания случайного пароля, который состоит из одной заглавной буквы, одной строчной буквы и одной цифры, вы можете сделать что-то вроде этого:

( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
#=> "Kc5zOGtM0H796QgPp8u2Sxo1"
Питер
источник
И если вы хотите ввести определенное число в каждом диапазоне, вы можете сделать что-то вроде этого:( ('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"
Джошуа Пинтер
3

Недавно я делал что-то подобное, чтобы сгенерировать 8-байтовую случайную строку из 62 символов. Символы были 0-9, az, AZ. У меня был их массив, так как я делал 8 циклов и выбирал случайное значение из массива. Это было внутри приложения Rails.

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

Странно то, что я получил много дубликатов. Теперь случайно это почти никогда не должно происходить. 62 ^ 8 огромен, но из 1200 или около того кодов в БД у меня было много дубликатов. Я заметил, что они происходят на часовых границах друг друга. Другими словами, я мог бы увидеть дуплет в 12:12:23 и 2:12:22 или что-то в этом роде ... не уверен, является ли время проблемой или нет.

Этот код был в до создания объекта ActiveRecord. Перед созданием записи этот код запускается и генерирует «уникальный» код. Записи в БД всегда создавались надежно, но код ( strв приведенной выше строке) дублировался слишком часто.

Я создал сценарий для выполнения 100000 итераций этой строки с небольшой задержкой, поэтому потребуется 3-4 часа в надежде увидеть какой-то повторяющийся шаблон на почасовой основе, но ничего не увидел. Я понятия не имею, почему это происходило в моем приложении Rails.

erik
источник