良心又好用,Linux内置的shuf,解决你80%需要随机的问题
引言
作为大多数熟练的 bash 程序员,都有可能没有听说过 shuf 这个指令。
不管你用或者不用,它都静静的躺在那里,从Linux发行版开始,它已经内置在指令集里了。
敢于冒险的人找到了 shuf,从此也改变了自己对他的看法。

shuf 是什么
在本文中我们尝试深入的了解 shuf 这个指令。
shuf是一个类似sort的命令行实用程序,包含在Coreutils中。您可能已经猜到,它用于伪随机给定的输入,就像您洗牌一样。你猜对了!
字如其人,它的名称也跟它的功能一样一目了然。
找到了这个指令,那就用起来吧,像其他大多数指令一样,在终端命令行里用 --help 打印出它的可选项。

shuf在哪些领域比较有用呢?有三个方面。
- 文件 file。
- 列表 list。
- 区间 range。
每种方式都有其优点,提前预习下 shuf 指令的用法,可以尽量避免使用中出现的错误。
操作文件
文件操作是下坡最常用的方式。选项中包含 -e 或者 -i,默认为文件操作。也就是说,命令行告诉该指令要输入的是一个文件。
文件来源可以是标准输入,或者是手动指定的文件路径。
参数列表的最后一个参数,也就可能是路径名或文件名。如果省略参数,则视为从标准输入读取。
下面是一些示例,明确指定文件来源。
标准输入隐式作为文件
这样,我们就从shuf命令的参数中省略了file。根据约定,您可以选择 - 来代替文件,以指示将该文件作为标准输入。
seq 3 | shuf
输出内容为,
1 3 2
标准输入显式作为文件
在命令行执行以下指令,
seq 3 | shuf -
输出内容如下,
3 1 2
我们可以看到上述两种方式。所达到的效果是一样的。
明确指定文件名
通过这种方式,我们在shuf命令的参数中指定一个文件名作为文件。下面是几个使用文件的文件shuf示例。
从终端输入
执行以下指令,
shuf /dev/fd/1

/dev/fd/1 其实就是类UNIX系统中的标准输入。命令行 Enter 之后。会停留在输入窗口。如上图所示,输入任意字符串。最后按 Ctrl + D 结束输入。shuf 将输入的文本随机打断并输出。
打乱文件的行
输入以下指令,
seq 3 > tmp.txt
shuf tmp.txt
rm -f tmp.txt
输出内容如下,
2 1 3
列表
在这种方式中,我们操作一个文件或通过管道输入到shuf命令中。通过这种方式,我们允许使用 -e 选项将输入行指定为shuf命令的参数,从而强制 shuf 作为列表 shuf 进行操作。
用法如下,
Usage: shuf -e [OPTION]... [ARG]...
在终端输入一些命令,
shuf -e 1 2 3
输出内容如下,
1 3 2
上述指令作用等同于
seq 3 | shuf -
使用变量作为参数
在终端输入以下指令,
var="1 2 3";
shuf -e ${var}
这个没有这个例子很简单,就是使用了 bash 变量进行操作。变量中存储了一个列表。
当然了,生成列表也可以用 bash 内置的方式。
shuf -e {1..3}
输出内容如下,
1 2 3
bash 的一些其他玩法,
shuf -e $( seq 3 )
本质上与命令符、管道、重定向,或文件内容读取原理是一致的。
区间
最后一种方法与前面介绍的方法不同。它不需要在命令行中指定文件或参数,而是需要一个整数范围。-i 选项强制 shuf 作为 range shuf 操作。
区间 shuf 生成一个按随机顺序排列的整数范围。
用法如下,
Usage: shuf -i LO-HI [OPTION]...
先举一个例子,
shuf -i 1-3
输出内容如下,
2 3 1
一些高级选项
下面列出的这些高级选项,在 bash 脚本编程中可能会很有用。
限制输出行数
运行以下指令,
shuf -i 1-3 -n 1
输出内容如下,
3
我们使用参数 -n 指定输出的行数。本例中 -n 等于 1,那么仅输出一行。
指定输出文件
像其他一些Linux中的指令一样,-o 用于指定输出文件名。举例说明,
shuf -i 1-3 -n 1 -o filter.txt
cat filter.txt
rm -f filter.txt
输出内容如下,
1
流操作
为了创建连续的输出行流,我们使用-r选项,如下所示。
shuf -e {0,1} -r | xargs -i echo -n "{}"
输出内容如下,
000101101010101101010110000101111010001010111001110…
使用\0代替\n用作分隔符
使用\0作为分隔行,仅需要使用 -z 选项。举例如下:
seq 3 | tr '\n' '\0' | shuf -z
输出内容如下:
213
如果没有shuffle,bash随机函数长什么样?
下面使用 gawk 实现类似 shuf 的功能,我们看看区别在哪里。
闲言少叙,直接上脚本。
gawk-shuf() {
gawk -v random=${RANDOM} '
function randInt() {
return int(rand()*1000)
}
function case_numeric_compare(i1, v1, i2, v2, l, r) {
l = int(v1)
r = int(v2)
if(l<r) return -1
else if(l==r) return 0
else return 1
}
BEGIN {
count=1
srand(random)
}
{
rank[count]=randInt()
line[count]=$(0)
count++
}
END {
asorti(rank,order,"case_numeric_compare")
for(i=0;i<count;i++) {
print line[order[i]]
}
}
' -
}
if [ ${#} -eq 0 ]
then
true
else
exit 1 # wrong args
fi
gawk-shuf
功能是一样的,但是效率很低,维护起来很麻烦,对不对?
写在最后
大神们都已经准备好工具了,我们只用把它拿出来使用,不要重复造轮子了。