从awk内部,我想根据需要快速生成一个相当随机(即随机但不加密)的 X 个字母数字字符串。
在 Ruby 中,我可以这样做:
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)}'
印刷:
7Ntz5NF5juUL7tGmYQhsc
kaOzO1aIxkW5rmJ9CaKtD
49SpdFTibXR1WPWV7li6c
PT862YZQd0dOIaFOIY0d1
vYktRXkdsj38iH3s2WKI
3nQZ7cCVEXvoaOZvm6mTR
为了进行时间比较,Ruby 可以在大约 9 秒内生成 1,000,000 个唯一字符串(无重复)。
考虑到这一点,我在 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)}}'
这不起作用——相同的种子,相同的字符串结果。打印:
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
现在尝试/dev/urandom
阅读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) }'
一切按预期进行。打印:
sYY195x6fFQdYMrOn1OS
9mv7KwtgdUu2DgslQByo
LyVvVauEBZU2Ad6kVY9q
WFsJXvw8YWYmySIP87Nz
AMcZY2hKNzBhN1ByX7LW
但是现在的问题是管道od -A n -t u1 -N " arg " /dev/urandom
真的很慢——除了少量的字符串之外无法使用。
知道如何修改其中一个 awk 以便:
- 在大多数平台上运行(即默认 POSIX 套件);
- 可以快速生成X长度的合理随机字符串。
这个问题已被问过几次:
- 如何使用 awk 将一个字符串替换为一个长度为 48 个字符的随机字母数字字符串,答案是使用外部工具——太慢了;
- 用 awk 用随机模式替换给定的模式,但这是一个随机整数并且不使用
srand
; - 在 awk 内部执行命令(生成随机字符串)但再次使用 shell 管道(太慢)和仅限 Linux。
我无法访问 Ruby,但在我的(显然很慢!)系统上,@dawgs 答案中的 awk 脚本需要 24 秒才能运行,而这个只需要 5 秒:
因此,如果您想生成大量字符串,则请先创建一个可能字母的数组,然后使用该数组进行索引,
rand()
而不是调用sprintf()
每个字符串的每个字母。由于使变量(如迭代变大)在内存[重新]分配方面很慢,因此可以通过设置每个字符(而不是构建字符串)
s
使脚本速度提高约 20% :OFS=""
$i
只要您不需要
$0
其他任何东西。使用 awk 1 并执行以下操作:
我将其重写(查看 Ed Morton 的答案)为:
与 Ruby(大幅改进)相比:
因此 Ruby 的速度更快一些 — — 正如预期的那样。(但是如果您使用
gawk
vs,awk
gawk 将在 6.2 秒内完成。)我不确定我是否相信 Ruby 计时。当我在服务器上运行它时,它更类似于 gawk。但我正在报告它。Apple M3 PowerBook。
i5-8350U 上的 Xubuntu 24.04.1
我刚刚发现:https ://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string ,其中有一个使用相同方法的答案。请注意,命令
LC_ALL=C
的设置tr
可能是可取的。为了允许可变长度输出,为参数选择合适的大/小值
fold
并累积少量getline
可能几乎一样快。也许两倍于典型所需长度可能是一个合理的选择?所以对于 20,如下所示:当然
od
,运行 或任何外部进程 100 万次都需要时间。但实际上您根本不需要 Awk。如果您需要与 base64 提供的字母表不同的字母表,那么可以使用类似的工具,
base85
但是这些工具并不那么普遍。值得注意的是,base64 字母表包括
/
和+
。如果这些不可接受,您可以添加一个简单的tr
后处理步骤:分布的随机性将再次略有减弱,但可能不足以产生任何实际差异。
我这里没有 Ruby,所以我无法直接比较,但在我的 Debian VM 上执行此操作的时间是
为了进行间接比较,Ed Morton 的第一个 Awk 脚本在这里实际需要 2 秒。添加
tr
管道后,实际时间缩短至 0.081 秒。这符合GNU Awk 用户手册中关于
srand
最后四个字尤其重要,因为你正在规定