Cara menghasilkan string acak di Ruby


747

Saat ini saya sedang membuat string huruf kapital pseudo-acak 8-karakter untuk "A" .. "Z":

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

tapi itu tidak terlihat bersih, dan itu tidak bisa diajukan sebagai argumen karena itu bukan pernyataan tunggal. Untuk mendapatkan string case campuran "a" .. "z" plus "A" .. "Z", saya mengubahnya menjadi:

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

tapi sepertinya sampah.

Adakah yang punya metode yang lebih baik?


Saya tidak mengerti mengapa Anda peduli bahwa "karena ini bukan pernyataan tunggal, itu tidak dapat diajukan sebagai argumen". Mengapa tidak menjadikannya metode utilitas atau pembantu?
David J.

1
Misalkan ada metode untuk mereset kata sandi pengguna dan memiliki argumen untuk kata sandi baru. Saya ingin mengirimkan string acak, dalam kode di atas saya memerlukan variabel tmp, sedangkan dalam contoh pernyataan tunggal di bawah ini saya dapat melakukan semuanya sebagai satu liner. Tentu metode jangka panjang bisa bagus, terutama jika saya membutuhkan yang serupa di sana-sini, tetapi kadang-kadang Anda hanya menginginkannya, satu kali, selesai.
Jeff

Tidak, Anda tidak harus menggunakan variabel sementara. Coba ini: di reset_user_password!(random_string)manadef random_string; SecureRandom.urlsafe_base64(20) end
David J.

8 huruf adalah kata sandi yang sangat lemah. Mengingat md5sum PC modern dapat memulihkan kata sandi dalam 30 detik . Bagaimana dengan sesuatu yang lebih lamasecurerandom.urlsafe_base64
Kolonel Panic

1
Mengapa ini memiliki begitu banyak jawaban? Bukannya itu bukan pertanyaan yang berguna, tapi saya ingin tahu bagaimana ini menarik perhatian.
Lou

Jawaban:


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

Saya menghabiskan terlalu banyak waktu bermain golf.

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

Dan yang terakhir bahkan lebih membingungkan, tetapi lebih fleksibel dan membuang lebih sedikit siklus:

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

205
34 karakter dan super cepat: ('a'..'z').to_a.shuffle[0,8].join. Perhatikan bahwa Anda perlu Ruby> = 1.9 hingga shuffle.
fny

20
Memanfaatkan pustaka yang ada lebih disukai kecuali Anda memiliki driver untuk membuat pustaka Anda sendiri. Lihat SecureRandomsebagai satu contoh, dalam jawaban lainnya.
David J.

28
@faraz metode Anda tidak secara fungsional sama, itu tidak acak dengan penggantian.
michaeltwofish

35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinuntuk menghasilkan string acak dengan huruf dan angka.
Robin

31
randbersifat deterministik dan dapat diprediksi. Jangan gunakan ini untuk menghasilkan kata sandi! Gunakan salah satu SecureRandomsolusi sebagai gantinya.
pensil

800

Mengapa tidak menggunakan SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom juga memiliki metode untuk:

  • base64
  • random_bytes
  • angka acak

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


11
base64 akan, tetapi tidak hex seperti dalam contohnya
Jeff Dickey

67
Ngomong-ngomong, ini adalah bagian dari stdlib dalam versi 1.9 dan 1.8 terbaru, jadi kita bisa require 'securerandom'mendapatkan SecureRandompembantu yang rapi ini :)
J -_- L

21
BTW SecureRandom telah dihapus dari ActiveSupport dalam versi 3.2. Dari changelog: "Dihapus ActiveSupport :: SecureRandom yang mendukung SecureRandom dari perpustakaan standar".
Marc

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")akan menghasilkan string dengan 0-9a-z (36 karakter) yang SELALU 12 karakter. Ubah 12 hingga berapa pun panjang yang Anda inginkan. Sayangnya tidak ada cara untuk hanya menggunakan AZ Integer#to_s.
Gerry Shaw

1
@ stringo0 itu salah. Jika Anda ingin melewati +fGH1URL, Anda hanya perlu menyandikan URL-nya seperti Anda akan nilai APAPUN yang melalui URL: %2BfGH1
nzifnab

247

Saya menggunakan ini untuk menghasilkan string ramah URL acak dengan panjang maksimum yang dijamin:

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

Ini menghasilkan string acak dari huruf kecil az dan 0-9. Ini tidak sangat dapat dikustomisasi tetapi pendek dan bersih.


4
+1 untuk versi terpendek (yang tidak memanggil binari eksternal ^^). Jika string acak tidak menghadap publik, saya kadang-kadang bahkan hanya menggunakan rand.to_s; jelek, tapi berhasil.
Jo Liss

12
Ini adalah solusi yang hebat (dan cepat juga), tetapi kadang-kadang akan menghasilkan string di bawah length panjang, kira-kira sekali dalam ~ 40
Brian E

7
@ Brian E ini akan menjamin angka yang Anda inginkan: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (panjang-1) dikonversi ke basis 36 adalah 10 ** (panjang-1), yang merupakan nilai terkecil yang memiliki panjang digit yang Anda inginkan.
Eric Hu

11
Ini adalah versi yang selalu menghasilkan token dengan panjang yang diinginkan:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon

3
Ini memunculkan kesalahan bagi saya di Rails 4 dan Ruby 2.1.1: NameError: undefined local variable or method length 'for main: Object`
kakubei

175

Solusi ini menghasilkan serangkaian karakter yang mudah dibaca untuk kode aktivasi; Saya tidak ingin orang membingungkan 8 dengan B, 1 dengan I, 0 dengan O, L dengan 1, dll.

# 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
Apakah 'U' ambigu atau apakah itu salah ketik?
gtd

10
@ gtd - Yap. U dan V adalah ambigvovs.
colinm

1
@colinm V ada di sana.
gtd

8
Untuk SecureRandom.random_number(charset.size)rand(charset.size)
amannya

1
Saya hanya mencoba untuk memperbaiki ini, dengan menambahkan huruf kecil dan / atau beberapa karakter khusus (shift + num), dan mendapat daftar berikut: %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 ! @ # $ % ^ & * +}dan %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 ! @ # $ % ^ & * }(daftar pertama tidak termasuk huruf kecil, tetapi lebih lama) Agak menarik bagaimana itu berhasil.
dkniffin

130

Yang lain menyebutkan sesuatu yang serupa, tetapi ini menggunakan fungsi URL safe.

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

Hasilnya mungkin mengandung AZ, az, 0-9, “-” dan “_”. “=” Juga digunakan jika bantalan benar.


Inilah yang saya cari. Terima kasih!
Dan Williams

69

Sejak Ruby 2.5, sangat mudah dengan SecureRandom.alphanumeric:

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

Ini menghasilkan string acak yang mengandung AZ, az dan 0-9 dan karenanya harus berlaku di sebagian besar kasus penggunaan. Dan mereka dihasilkan secara acak aman, yang mungkin bermanfaat juga.


Ini adalah tolok ukur untuk membandingkannya dengan solusi yang paling banyak mendapat dukungan:

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)

Jadi randsolusinya hanya membutuhkan sekitar 3/4 dari waktu SecureRandom. Itu mungkin penting jika Anda menghasilkan banyak string, tetapi jika Anda hanya membuat beberapa string acak dari waktu ke waktu, saya akan selalu menggunakan implementasi yang lebih aman karena juga lebih mudah untuk dipanggil dan lebih eksplisit.


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

Hasilkan string 8 huruf acak (mis. NVAYXHGR)

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

Menghasilkan string 8 karakter acak (mis. 3PH4SWF2), tidak termasuk 0/1 / I / O. Ruby 1.9


4
Satu-satunya masalah adalah setiap karakter dalam hasilnya unik. Membatasi nilai yang mungkin.
tybro0103

1
Jika permintaan fitur ini diteruskan, Ruby 1.9.x dapat berakhir dengan #sample untuk pengambilan sampel tanpa penggantian dan #choice untuk pengambilan sampel dengan penggantian.
David J.

Ini adalah kesalahan, saya pikir Anda perlu ... [*("A".."Z")]'; ((bukan qoutes tunggal))
jayunit100

bisa Anda ceritakan bagaimana saya dapat stubini untuk rspeclulus.
Sinscary

31

Saya tidak ingat di mana saya menemukan ini, tetapi sepertinya yang terbaik dan paling sedikit proses intensif bagi saya:

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


Apakah tidak ada 0 dan 1 yang hilang dari ini?
sunnyrjuneja

Sepertinya itu bisa menjadi tempat saya menemukannya.
Travis Reeder

Dan ya, sepertinya 0 dan 1 hilang.
Travis Reeder

4
The 0dan 1dan Odan Isengaja hilang karena karakter-karakter itu ambigu. Jika kode semacam ini digunakan untuk menghasilkan sekumpulan karakter yang perlu disalin pengguna, yang terbaik adalah mengecualikan karakter yang mungkin sulit dibedakan dari konteks.
Andrew

28
require 'securerandom'
SecureRandom.urlsafe_base64(9)

SecureRandom adalah lib yang sangat baik. Saya tidak tahu itu ada di sana. Terima kasih.
Dogweather

alternatif skool lama (dan jelek):Random.new.bytes(9)
tardate

4
btw, urlsafe_base64 mengembalikan string sekitar 4/3 panjang yang ditunjukkan. Untuk mendapatkan string yang tepat dan panjang karakter, coban=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardate

25

Jika Anda ingin string dengan panjang tertentu, gunakan:

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

Ini akan menghasilkan string acak yang 2nberisi panjang 0-9dana-f


Itu tidak menghasilkan string yang panjang n, itu benar-benar menghasilkan string yang 4/3 dari n.
Omar Ali

@ OmarAli Anda salah. Sesuai dokumentasi Ruby .hexuntuk 2n. Dokumentasi: SecureRandom.hex menghasilkan string heksadesimal acak. Argumen n menentukan panjang, dalam byte, dari nomor acak yang akan dihasilkan. Panjang string heksadesimal yang dihasilkan adalah dua kali dari n .
Rihards


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

Menarik, tetapi sedikit lebih mahal secara komputasi
Jeff

Juga tidak ada kapasitas untuk lingkup karakter yang terbatas.
Kent Fredric

3
Perlu diingat bahwa hex digest hanya mengembalikan 0-9 dan karakter af.
webmat

11

Berikut ini adalah satu baris kode sederhana untuk string acak dengan panjang 8:

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

Anda juga dapat menggunakannya untuk kata sandi acak yang panjangnya 8:

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

2
Anda tidak boleh menggunakan ini untuk menghasilkan kata sandi sendiri, karena metode ini tidak akan pernah mengulangi karakter. Karena itu Anda hanya menggunakan P(36, 8) / 36^8 = 0.4ruang karakter yang dimungkinkan untuk 8 karakter (~ 2x lebih mudah untuk memaksa) atau P(36, 25) / 36^25 = 0.00001dari ruang karakter yang mungkin untuk 25 karakter (~ 100.000x lebih mudah untuk memaksa).
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
The mapsolusi adalah benar-benar bagus!
Misha Moroshko

Anda dapat menanyakan #sampleberapa banyak elemen yang Anda inginkan. Misalnya ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp

Itu sebenarnya salah. sample(10)memberi Anda 10 sampel unik. Tapi Anda ingin mereka mengulanginya. Tapi saya akan gunakan Array.new(10).mapuntuk kinerja
Saša Zejnilović

Saya ingin alfanumerik dengan huruf besar dan kecil. Saya juga sudah beralih menggunakan Array.newsintaks bloknya. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow

8

Waspada: randdapat diprediksi untuk penyerang dan karena itu mungkin tidak aman. Anda harus menggunakan SecureRandom jika ini untuk menghasilkan kata sandi. Saya menggunakan sesuatu seperti ini:

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

Ini mungkin solusi aman "paling". SecureRandom mencoba menggunakan API keamanan mendasar yang disediakan oleh sistem operasi. Jika Anda memiliki OpenSSL, ia akan menggunakannya, jika Anda menggunakan Windows, itu akan menjadi opsi terbaik di sana. Saya terutama menyukai solusi ini karena memungkinkan Anda untuk menentukan serangkaian karakter untuk digunakan. Meskipun itu tidak akan berfungsi jika rangkaian karakter Anda lebih panjang dari nilai maksimum byte: 255. Saya sarankan melihat kode sumber untuk SecureRandom di doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc / ...
Breedly

8

Berikut adalah satu kode sederhana untuk kata sandi acak dengan panjang 8:

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

7

Metode lain yang saya suka gunakan:

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

Tambahkan ljustjika Anda benar-benar paranoid tentang panjang string yang benar:

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

Bahkan lebih baik untuk mengambil bagian paling tidak penting dari angka acak dengan menggunakan sisi kanan string: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff

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

Sesuatu dari Rancangan


Mengapa mengganti karakter string menggunakan .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain

Karakter khusus karena mereka bukan URL aman. Dan saya / saya atau O / 0 karena mereka sangat mudah bingung jika Anda menggunakan teknik ini untuk menghasilkan kata sandi pengguna yang dapat dibaca.
ToniTornado

Fungsi itu memiliki sedikit bias terhadap karakter tertentu. Juga untuk panjang lainnya (mis. 16), karakter terakhir tidak akan acak. Inilah cara untuk menghindarinya. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman

5

Saya pikir ini adalah keseimbangan yang baik antara keringkasan, kejelasan, dan kemudahan modifikasi.

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

Mudah dimodifikasi

Misalnya, termasuk digit:

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

Heksadesimal huruf besar:

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

Untuk serangkaian karakter yang benar-benar mengesankan:

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

1
saya akan merekomendasikan ini untuk hanya menggunakan huruf kapital + angka, juga menghapus charset yang "membingungkan" = (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
luster

5

Cukup tambahkan sen saya di sini ...

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

3
NB: ini tidak selalu menghasilkan string persis + panjang + panjang - mungkin lebih pendek. Itu tergantung pada jumlah yang dikembalikan olehrand
tardate

5

Anda dapat menggunakan String#randomdari Aspek Ruby Gem facets.

Ini pada dasarnya melakukan ini:

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

Favorit saya adalah (:A..:Z).to_a.shuffle[0,8].join. Perhatikan bahwa shuffle memerlukan Ruby> 1.9.


4

Solusi ini membutuhkan ketergantungan eksternal, tetapi tampaknya lebih cantik daripada yang lain.

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

4

Diberikan:

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

Ekspresi tunggal, dapat disampaikan sebagai argumen, memungkinkan karakter duplikat:

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

3

Saya suka jawaban Radar, sejauh ini, saya pikir. Saya akan mengubah sedikit seperti ini:

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

Kenapa tidak digunakan (CHARS*length).sample(length).join?
niels

@ elemen Saran ini akan menghasilkan string tertimbang , yang mendukung karakter yang tidak diulang. Misalnya, jika CHARS=['a','b']metode Anda menghasilkan "aa"atau "bb"hanya 33% dari waktu, tetapi "ab"atau "ba"67% dari waktu. Mungkin itu bukan masalah, tapi perlu diingat!
Tom Lord

Poin yang bagus, @TomLord, saya pikir saya tidak benar-benar menyadari bahwa ketika saya memposting saran itu (walaupun saya harus mengakui saya tidak ingat memposting itu sama sekali: D)
niels

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

3

2 sen saya:

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


3

2 solusi untuk string acak yang terdiri dari 3 rentang:

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

Satu Karakter dari masing-masing Rentang.

Dan jika Anda memerlukan setidaknya satu karakter dari setiap rentang, seperti membuat kata sandi acak yang memiliki satu huruf besar, satu huruf kecil dan satu digit, Anda dapat melakukan sesuatu seperti ini:

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

Dan jika Anda ingin menegakkan sejumlah tertentu dari setiap rentang, Anda dapat melakukan sesuatu seperti ini:( ('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

Saya melakukan sesuatu seperti ini baru-baru ini untuk menghasilkan string acak 8 byte dari 62 karakter. Karakternya 0-9, az, AZ. Saya memiliki array seperti looping 8 kali dan memilih nilai acak dari array. Ini ada di dalam aplikasi Rails.

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

Yang aneh adalah saya mendapat duplikat dalam jumlah yang banyak. Sekarang secara acak ini seharusnya tidak pernah terjadi. 62 ^ 8 sangat besar, tetapi dari 1.200 kode di db saya punya banyak duplikat. Saya perhatikan mereka terjadi pada batas jam satu sama lain. Dengan kata lain saya mungkin melihat duple pada 12:12:23 dan 2:12:22 atau sesuatu seperti itu ... tidak yakin apakah waktu adalah masalah atau tidak.

Kode ini di sebelum membuat objek ActiveRecord. Sebelum catatan dibuat, kode ini akan menjalankan dan menghasilkan kode 'unik'. Entri dalam DB selalu diproduksi dengan andal, tetapi kode ( strpada baris di atas) terlalu sering diduplikasi.

Saya membuat skrip untuk menjalankan melalui 100000 iterasi baris di atas ini dengan penundaan kecil sehingga akan memakan waktu 3-4 jam berharap untuk melihat semacam pola pengulangan setiap jam, tetapi tidak melihat apa pun. Saya tidak tahu mengapa ini terjadi di aplikasi Rails saya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.