如何从文件中随机读取一行?

270

在shell脚本中,从文件中随机读取一行的简单方法是什么?


每行是否填充为固定长度? - Tracker1
不,每行字符数是可变的。 - Newbie Prog
大文件:https://dev59.com/iIjca4cB1Zd3GeqP2NKh - Ciro Santilli OurBigBook.com
13个回答

395
您可以使用 shuf:
shuf -n 1 $FILE

还有一个叫做rl的实用工具。在Debian中,它在randomize-lines软件包中,正好可以满足你的需求,尽管并不是所有发行版都可用。在其主页上,它实际上推荐使用shuf(我相信当它被创建时,shuf还不存在)。shuf是GNU coreutils的一部分,而rl则不是。

rl -c 1 $FILE

2
谢谢 shuf 的提示,它在 Fedora 中是内置的。 - Cheng
5
此外,如果处理相当大的文件 - 80kk行 - sort -R 肯定会让人等待很长时间,而 shuf -n 则可以立即执行。 - Rubens
23
在OS X上安装Homebrew中的“coreutils”,您可以获得“shuf”。可能称为“gshuf”而不是“shuf”。 - Alyssa Ross
2
同样地,您可以在 OS X 上使用 randomize-lines 命令,方法是 brew install randomize-lines; rl -c 1 $FILE - Jamie
4
注意,shufGNU Coreutils 的一部分,因此在 *BSD 系统(或 Mac?)中不一定会默认提供。@Tracker1 下面的 perl 一行代码更具可移植性(而且根据我的测试,速度略快)。 - Adam Katz
显示剩余12条评论

74

另一种选择:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1

28
${RANDOM} 只能生成小于32768的数字,因此不要将其用于大文件(例如英语词典)的生成。 - Ralf
3
由于模运算的存在,这并不会为每行提供完全相同的概率。如果文件长度远小于32768(且可以被该数字整除),那么这几乎不重要,但也可能值得注意。 - Anaphory
11
你可以使用 (${RANDOM} << 15) + ${RANDOM} 来扩展到30位随机数。这样可以显著减小偏差,并且适用于包含多达10亿行的文件。 - nneonneo
@nneonneo:非常酷的技巧,不过根据这个链接,应该使用OR运算符而不是加号来处理${RANDOM}。https://dev59.com/MHE85IYBdhLWcg3w436o#19602060 - Jay Taylor
“+”和“|”是相同的,因为${RANDOM}的定义范围是0..32767。 - nneonneo
这会带来严重的性能损失,因为它需要计算行数以确保读取到正确的位置。 - Charles Duffy

74
sort --random-sort $FILE | head -n 1

(我更喜欢上面的随机排序方法 - 我甚至不知道它的存在,我自己也永远找不到那个工具)


10
+1 我喜欢它,但你可能需要一个非常新的 sort,因为它在我的任何系统上都无法工作(CentOS 5.5,Mac OS 10.7.2)。此外,使用 cat 是无用的,可以简化为 sort --random-sort < $FILE | head -n 1 - Steve Kehlet
5
这个方法比较慢,因为需要使用 sort 对整个文件进行洗牌(shuffle),然后再将结果输出给 head。相反,shuf 会直接选择文件中的随机行,速度更快。 - Bengt
1
@SteveKehlet 顺便说一下,sort --random-sort $FILE | head 是最好的选择,因为它允许直接访问文件,可能实现高效并行排序。 - WaelJ
5
“--random-sort”和“-R”选项是特定于GNU sort的(因此它们不能与BSD或Mac OS中的“sort”命令一起使用)。 GNU sort在2005年学习了这些标志,因此您需要GNU coreutils 6.0或更高版本(例如CentOS 6)才能使用它们。 - RJHunter
@Bengt:在shuf将整个文件读入内存之前,什么都没有被写入。即使文件不适合内存,sort也可以工作。 - jfs
显示剩余3条评论

31

这很简单。

cat file.txt | shuf -n 1

虽然比起单独使用“shuf -n 1 file.txt”命令要慢一点


2
最佳答案。我不知道这个命令。请注意,-n 1指定了1行,您可以将其更改为多于1行。 shuf也可以用于其他事情; 我只是将ps auxgrep与它一起使用,以随机杀死部分匹配名称的进程。 - sudo

20

perlfaq5:如何从文件中选择一行随机? 以下是来自“Camel Book”的水塘取样算法:

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

相比于将整个文件读入内存,这种方法在空间上具有显著的优势。您可以在《计算机程序设计艺术》第2卷第3.4.2节中找到此方法的证明,作者是Donald E. Knuth。


1
仅为包容目的(以防所引用的站点崩溃),这是 Tracker1 指向的代码:"cat filename | perl -e 'while (<>) { push(@,$); } print @[rand()*@];';" - Anirvan
3
这是一种无用的使用猫的方法。 这是对perlfaq5中代码的微小修改(并且得益于Camel书): perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' 文件名 - Mr. Muskrat
错误...指的是链接的网站 - Nathan Fellman
我刚刚对这段代码的N行版本进行了基准测试,与shuf进行了比较。Perl代码速度略快(用户时间快8%,系统时间快24%),尽管从个人经验来看,我发现Perl代码“似乎”不太随机(我使用它编写了一个点唱机)。 - Adam Katz
2
更多值得思考的问题:shuf将整个输入文件存储在内存中,这是一个可怕的想法,而这段代码只存储一行,因此该代码的限制是INT_MAX(2^31或2^63,具体取决于您的架构)的行数,假设其选择的任何潜在行都适合内存。 - Adam Katz
这是awk的等效命令。这两个答案(perl或awk)都比被接受的答案更好-具有可移植性,速度快,并且能够轻松管理大型文件。awk 'BEGIN{srand()}{rand()*NR<1&&l=$0}END{print l}' file或者some_input | awk 'BEGIN{srand()}{rand()*NR<1&&l=$0}END{print l}' - keithpjolley

11

使用 Bash 脚本:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}

1
随机数可以为0,但sed命令第一行需要1。使用sed -n 0p会返回错误。 - asalamon74
@asalamon74:谢谢 @blabla999:如果我们将其制作成一个函数,对于$1来说可以,但为什么不计算NUM呢? - Paolo Tedesco
检测到无用的cat使用,wc可以直接处理文件。 - Hasturkun
变量名应该加引号,特别是$FILE。这里的花括号是多余的。我建议使用小写或混合大小写的变量名,以避免与shell或环境变量可能发生的名称冲突。 - Dennis Williamson
如果一个文件有32769行或更多行,最后的行将永远不会被选中。wc -l 不应该有空格。 - Lri
显示剩余5条评论

4

单行Bash命令:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

小问题:文件名重复。


3
轻微的问题。在 /usr/share/dict/words 上执行此操作往往会偏向以“A”开头的单词。通过尝试,我得到了大约90%的“A”单词和10%的“B”单词。文件中还没有以数字开头的单词。 - bibby
wc -l < test.txt 避免了需要使用管道到 cut 的情况。 - fedorqui

3
这里有一个简单的Python脚本可以完成这个任务:
import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

使用方法:

python randline.py file_to_get_random_line_from

1
这个代码不太行。它只会输出一行就停止了。为了让它正常工作,我做了以下修改:import random, sys lines = open(sys.argv[1]).readlines()
for i in range(len(lines)):
rand = random.randint(0, len(lines)-1)
print lines.pop(rand),
- Jed Daniels
愚蠢的评论系统和糟糕的格式。评论中的格式化曾经不起作用吗? - Jed Daniels
randint是包含的,因此len(lines)可能会导致IndexError。您可以使用print(random.choice(list(open(sys.argv[1]))))。还有一种内存高效的蓄水池抽样算法 - jfs
2
非常占用空间;考虑一个3TB的文件。 - Michael Campbell
@MichaelCampbell:我之前提到的蓄水池抽样算法可以处理3TB的文件(如果行大小有限制)。 - jfs
使用py很不错。-l将输入的行分配给列表lpy自动导入stdlib模块,因此您可以执行cat $FILE | py -l "random.choice(l)"。试试:python -m this | py -l "random.choice(l)" ... 呃实际上只需py this | py -l "random.choice(l)" ;) - floer32

2

使用'awk'的另一种方法

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name

2
这段代码使用了 awk 和 bash($RANDOMbashism)。以下是一个纯 awk(mawk)方法,使用与 @Tracker1 引用的 perlfaq5 代码相同的逻辑:awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(哇,它甚至比 perl 代码还要!) - Adam Katz
该代码必须读取文件(wc)以获取行数,然后必须再次读取(部分)文件(awk)以获取给定随机行号的内容。I/O比获取随机数要昂贵得多。我的代码仅读取一次文件。awk的rand()问题在于它基于秒种子生成,因此如果您连续运行它太快,就会得到重复的结果。 - Adam Katz

1
一个在MacOSX上能够工作,并且应该也能在Linux上工作的解决方案:
N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

其中:

  • N 是您想要的随机行数。

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 --> 保存在file1中写入的行号,然后打印出file2相应的行。

  • jot -r $N 1 $(wc -l < $file) --> 使用jot在范围(1,文件总行数)内随机绘制N个数字(-r)。进程替换<()会让它看起来像是解释器的一个文件,所以前面例子中的file1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接