Як генерувати випадкову рядок у 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.