Як генерувати випадкову рядок у Ruby


746

Наразі я генерую 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}

але це схоже на сміття.

Хтось має кращий метод?


Я не розумію, чому ви хвилюєте це, "оскільки це не одне твердження, воно не може бути передане як аргумент". Чому б просто не зробити це корисним чи допоміжним методом?
Девід Дж.

1
Припустимо, існує метод скидання пароля користувача, і він має аргумент для нового пароля. Я хотів би передати випадкову рядок, у наведеному вище коді мені потрібна змінна tmp, тоді як у наведених нижче прикладах єдиного твердження я можу зробити все це як один вкладиш. Звичайно, корисний метод може бути хорошим у довгостроковій перспективі, особливо якщо мені потрібні подібні тут і там, але іноді ви просто хочете, щоб це було на місці, один раз, зроблено.
Джефф

Ні, вам не потрібно використовувати тимчасову змінну. Спробуйте так: reset_user_password!(random_string)деdef random_string; SecureRandom.urlsafe_base64(20) end
David J.

8 листів - це ганебно слабкий пароль. Враховуючи md5sum, сучасний ПК міг відновити пароль за 30 секунд . Як щодо чогось більш тривалогоsecurerandom.urlsafe_base64
полковник Паніка

1
Чому на це так багато відповідей? Не те, щоб це не корисне питання, але мені цікаво, як воно привернуло увагу.
Лу

Відповіді:


969
(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

204
34 символів і блискавично: ('a'..'z').to_a.shuffle[0,8].join. Зауважте, вам знадобиться Ruby> = 1,9 до shuffle.
fny

20
Використання існуючих бібліотек є кращим, якщо у вас немає драйвера для власної передачі. Дивіться SecureRandomяк один приклад, в іншому відповіді.
Девід Дж.

28
@faraz ваш метод функціонально не однаковий, він не випадковий із заміною.
міхаелвофі

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 також має методи для:

  • база64
  • random_bytes
  • random_number

див .: http://ruby-doc.org/stdlib-1.9.2/libdoc/securerandom/rdoc/SecureRandom.html


11
base64 хотів би, але не шістнадцятковий, як у його прикладі
Джефф Дікі

67
До речі, це частина stdlib в 1.9 та останніх версіях 1.8, тому можна просто require 'securerandom'отримати цього акуратного SecureRandomпомічника :)
J -_- L

21
BTW SecureRandom видалено з ActiveSupport у версії 3.2. З журналу змін: "Видалено ActiveSupport :: SecureRandom на користь SecureRandom зі стандартної бібліотеки".
Марк

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")буде генерувати рядок з 0-9a-z (36 символів), який ЗАВЖДИ 12 символів. Змініть 12 на будь-яку довжину. На жаль, жодного способу отримати AZ за допомогою Integer#to_s.
Геррі Шоу

1
@ stringo0 це неправильно. Якщо ви хотіли перейти +fGH1через URL-адресу, вам просто потрібно її кодувати URL-адресою так, як будь-яке значення, яке проходить через URL-адресу: %2BfGH1
nzifnab

247

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

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

Він генерує випадкові рядки з малого az та 0-9. Це не дуже настроюється, але він короткий і чистий.


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)
Адрієн Джартон

3
Це викриває для мене помилку в Rails 4 та Ruby 2.1.1: NameError: undefined local variable or method length 'for 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

3
Чи "U" неоднозначно чи це друкарська помилка?
gtd

10
@gtd - Так. U і V - амбігови.
colinm

1
@colinm V все ж там.
gtd

8
Для безпеки ви також хочете використовувати SecureRandom.random_number(charset.size)замістьrand(charset.size)
gtd

1
Я просто намагався покращити це, додавши малі регістри та / або якісь спеціальні символи (shift + число), і отримав такі списки: %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, “-” та “_”. "=" Також використовується, якщо підкладка є правдою.


Це саме те, що я шукав. Дякую!
Ден Вільямс

69

З 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. Це може мати значення, якщо ви генеруєте багато рядків, але якщо ви час від часу просто створюєте якусь випадкову рядок, я б завжди переходив із більш безпечною реалізацією, оскільки це також простіше зателефонувати і більш чітко.


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пройти.
Sinscary

31

Я не можу згадати, де я це знайшов, але це здається мені найкращим і найменш інтенсивним процесом:

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

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)

SecureRandom - відмінна робота. Я не знав, що це там. Дякую.
Dogweather

стара альтернатива skool (і потворніша):Random.new.bytes(9)
tardate

4
btw, 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


Він не генерує рядок довжини 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 та af символів.
вебмат

11

Ось один простий простий код для випадкового рядка довжиною 8:

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

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

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

2
Не слід використовувати це для генерування пароля, оскільки цей метод ніколи не повторить символ. Тому ви використовуєте лише P(36, 8) / 36^8 = 0.4можливий пробіл символів для 8 символів (~ 2 рази простіше, ніж груба сила) або P(36, 25) / 36^25 = 0.00001можливий пробіл символів для 25 символів (~ 100 000x простіший грубої сили).
Glacials

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
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
Тейлортвурлов

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 у документі: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc /…
Порожно

8

Ось один простий код для випадкового пароля довжиною 8:

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

7

Ще один метод, який я люблю використовувати:

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

Додайте, ljustякщо ви дійсно параноїчні щодо правильної довжини рядка:

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

Ще краще схопити найменш значну частину випадкового числа за допомогою правої частини рядка: rand (2 ** 64) .to_s (36) [- 10,10]
Джефф

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

Щось із Devise


Чому це використовується заміна символів рядків .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain

Спеціальні символи, оскільки вони не захищені URL-адресами. І l / I або O / 0, тому що їх дуже легко плутати, якщо використовувати техніку для створення читабельних паролів користувачів.
ToniTornado

Ця функція має трохи упередженості щодо певних символів. Також для інших довжин (наприклад, 16) останній символ не буде випадковим. Ось спосіб уникнути цього. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman

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']. Включають? (A)} (0 ... розмір) .map {charset [rand (charset.size)]} .join
блиск

5

Просто додаю сюди свої центи ...

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

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

4

Моя улюблена (:A..:Z).to_a.shuffle[0,8].join. Зауважте, що для переміщення потрібен Ruby> 1.9.


4

Це рішення потребує зовнішньої залежності, але здається гарнішим за інше.

  1. Встановіть драйвер для самоцвітів
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

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

Чому б не використовувати (CHARS*length).sample(length).join?
niels

@niels Ця пропозиція створить зважену рядок на користь символів, що не повторюються. Наприклад, якщо CHARS=['a','b']тоді ваш метод генерував би "aa"або "bb"лише 33% часу, але "ab"або "ba"67% часу. Можливо, це не проблема, але це варто пам’ятати!
Том Лорд

Хороший момент, @TomLord, я думаю, я насправді не усвідомлював, що коли я опублікував цю пропозицію (хоча мушу визнати, я взагалі не пам’ятаю, щоб розміщувати це повідомлення: D)
niels


3

Мої 2 копійки:

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


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 або близько кодів у db у мене було багато дублікатів. Я помітив, як вони відбуваються на годинних межах один одного. Іншими словами, я можу побачити дуель у 12:12:23 та 2:12:22 або щось подібне ... не впевнений, час у цьому питання чи ні.

Цей код був у створеному раніше об'єкті ActiveRecord. До створення запису цей код запускався та генерував би "унікальний" код. Записи в БД завжди вироблялися надійно, але код (str у наведеному вище рядку) дублювався надто часто.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.