De dentro do awk, quero gerar uma sequência de X caracteres alfanuméricos razoavelmente aleatórios (ou seja, aleatórios, mas não criptográficos) sob demanda e rapidamente.
Em Ruby, eu poderia fazer isso:
ruby -e '
def rand_string(len, min=48, max=123, pattern=/[[:alnum:]]/)
rtr=""
while rtr.length<len do
rtr+=(0..len).map { (min + rand(max-min)).chr }.
select{|e| e[pattern] }.join
end # falls out when min length achieved
rtr[0..len]
end
(0..5).each{|_| puts rand_string(20)}'
Impressões:
7Ntz5NF5juUL7tGmYQhsc
kaOzO1aIxkW5rmJ9CaKtD
49SpdFTibXR1WPWV7li6c
PT862YZQd0dOIaFOIY0d1
vYktRXkdsj38iH3s2WKI
3nQZ7cCVEXvoaOZvm6mTR
Para uma comparação de tempo, o Ruby pode produzir 1.000.000 de strings exclusivas (sem duplicatas) em aproximadamente 9 segundos.
Levando isso em consideração, tentei no awk:
awk -v r=$RANDOM '
# the r value will only be a new seed each invocation -- not each f call
function rand_string(i) {
s=""
min=48
max=123
srand(r)
while (length(s)<i) {
c=sprintf("%c", int(min+rand()*(max-min+1)))
if (c~/[[:alnum:]]/) s=s c
}
return s
}
BEGIN{ for (i=1; i<=5; i++) {print rand_string(20)}}'
Isso não funciona -- mesma semente, mesmo resultado de string. Imprime:
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
Agora tente ler /dev/urandom
com od
:
awk '
function rand_string(i) {
arg=i*4
cmd="od -A n -t u1 -N " arg " /dev/urandom" # this is POSIX
# ^ ^ unsigned character
# ^ ^ count of i*4 bytes
s=""
min=48
max=123
while (length(s)<i) {
while((cmd | getline line)>0) {
split(line, la)
for (e in la) {
if (la[e]<min || la[e]>max) continue
c=sprintf("%c", la[e])
if (c~/[[:alnum:]]/) s=s c
}
}
close(cmd)
}
return substr(s,1,i)
}
BEGIN {for(i=1;i<=5;i++) print rand_string(20) }'
Isso funciona como desejado. Imprime:
sYY195x6fFQdYMrOn1OS
9mv7KwtgdUu2DgslQByo
LyVvVauEBZU2Ad6kVY9q
WFsJXvw8YWYmySIP87Nz
AMcZY2hKNzBhN1ByX7LW
Mas agora o problema é que o pipe od -A n -t u1 -N " arg " /dev/urandom
está muito lento — inutilizável, exceto para um número trivial de strings.
Alguma ideia de como posso modificar um desses awks para que ele:
- Funciona na maioria das plataformas (ou seja, kit POSIX padrão);
- Pode produzir sequências razoavelmente aleatórias de comprimento X rapidamente.
Esta pergunta já foi feita algumas vezes:
- Como posso substituir uma string por uma string alfanumérica aleatória de 48 caracteres usando o awk onde a resposta é usar ferramentas externas -- muito lento;
- Substitua o padrão fornecido por um aleatório com awk, mas esse é um int aleatório e não usa
srand
; - Execute um comando (para gerar strings aleatórias) dentro do awk , mas novamente use o shell pipe (muito lento) e somente Linux.
Não tenho acesso ao Ruby, mas no meu sistema (aparentemente lento!) o script awk da resposta do @dawgs leva 24 segundos para ser executado, enquanto este leva 5 segundos:
então se você quiser produzir muitas strings, crie primeiro uma matriz das letras possíveis e depois indexe a matriz usando
rand()
em vez de chamarsprintf()
cada letra de cada string.Como tornar uma variável
s
iterativamente maior é lento em termos de [re]alocação de memória, você pode tornar o script cerca de 20% mais rápido ainda definindoOFS=""
cada$i
caractere em vez de criar uma string:contanto que você não precise
$0
de mais nada.Pegue o awk 1 e faça isto:
O que eu reescrevi (olhando para a resposta de Ed Morton) para ser:
VS o Ruby (bastante melhorado):
Então o Ruby é um pouco mais rápido — como esperado. (Mas se você usar
gawk
vs,awk
o gawk é concluído em 6,2 segundos.)Não tenho certeza se acredito no Ruby timing. Quando o executo em um servidor, é mais parecido com o gawk. Mas estou relatando. Apple M3 PowerBook.
Xubuntu 24.04.1 no i5-8350U
Acabei de encontrar: https://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string que tem uma resposta usando a mesma abordagem. Há uma nota de que a configuração
LC_ALL=C
para otr
comando pode ser desejável.Para permitir saída de comprimento variável, selecionar um valor adequadamente grande/pequeno para o
fold
parâmetro e acumular um pequeno número degetline
pode ser quase tão rápido. Talvez o dobro do comprimento típico desejado pode ser uma escolha razoável? Então para 20, algo como:Claro que executar
od
, ou qualquer processo externo, 1 milhão de vezes vai levar tempo. Mas você realmente não precisa do Awk aqui.Se você precisar de um alfabeto diferente do fornecido em base64, existem ferramentas como
base85
essa, que, no entanto, são menos onipresentes.Notavelmente, o alfabeto base64 inclui
/
e+
. Se esses forem inaceitáveis, você pode adicionar umatr
etapa simples de pós-processamento:A distribuição será então um pouco menos aleatória novamente, mas provavelmente não o suficiente para fazer alguma diferença real.
Não tenho Ruby aqui, então não posso comparar diretamente, mas os tempos para isso na minha VM Debian são
Para comparação indireta, o primeiro script Awk de Ed Morton leva 2 segundos em tempo real aqui. Adicionar o
tr
pipeline empurra isso para 0,081s em tempo real.Isso está em conformidade com o que o manual do usuário do GNU Awk diz sobre
srand
As últimas 4 palavras são especialmente importantes considerando que você está estipulando