有没有一种“规范”的方法?我一直在使用head-n|tail-1来完成这个任务,但我一直在想是否有一个Bash工具专门从文件中提取一行(或一系列行)。
我所说的“规范”是指一个主要功能就是这样做的程序。
有没有一种“规范”的方法?我一直在使用head-n|tail-1来完成这个任务,但我一直在想是否有一个Bash工具专门从文件中提取一行(或一系列行)。
我所说的“规范”是指一个主要功能就是这样做的程序。
当前回答
作为CaffeineConnisseur非常有用的基准测试答案的后续。。。我很好奇“mapfile”方法与其他方法相比的速度有多快(因为没有测试),所以我自己尝试了一个快速而肮脏的速度比较,因为我手边有bash 4。在我做这项测试时,我在顶部答案的一条评论中提到了“tail|head”方法(而不是head|tail),因为人们都在称赞它。我没有使用的测试文件的大小;我能在短时间内找到的最好的文件是一个14M的谱系文件(用空格分隔的长行,略低于12000行)。
短版本:mapfile看起来比cut方法快,但比其他任何方法都慢,所以我称它为无用的。tail|head,OTOH,看起来可能是最快的,尽管与sed相比,这种大小的文件差异并不大。
$ time head -11000 [filename] | tail -1
[output redacted]
real 0m0.117s
$ time cut -f11000 -d$'\n' [filename]
[output redacted]
real 0m1.081s
$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]
real 0m0.058s
$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]
real 0m0.085s
$ time sed "11000q;d" [filename]
[output redacted]
real 0m0.031s
$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]
real 0m0.309s
$ time tail -n+11000 [filename] | head -n1
[output redacted]
real 0m0.028s
希望这有帮助!
其他回答
以上所有答案都直接回答了这个问题。但这是一个不那么直接的解决方案,但可能是一个更重要的想法,可以引起人们的思考。
由于行长度是任意的,因此需要读取文件第n行之前的所有字节。如果您有一个巨大的文件或需要多次重复此任务,并且此过程非常耗时,那么您应该认真考虑是否应该首先以不同的方式存储数据。
真正的解决方案是有一个索引,例如在文件的开头,指示行开始的位置。您可以使用数据库格式,或者在文件开头添加一个表。或者,创建一个单独的索引文件,与大型文本文件一起使用。
例如,您可以为换行符创建一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
然后用tail读取,它实际上直接查找文件中的适当点!
例如获得线1000:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
这可能不适用于2字节/多字节字符,因为awk是“字符识别”的,但tail不是。我还没有对一个大文件进行测试。另请参阅此答案。或者,将文件拆分为更小的文件!
我有一个独特的情况,我可以对本页上提出的解决方案进行基准测试,因此我将此答案作为所提出解决方案的合并,并包含每个解决方案的运行时间。
设置
我有一个3.261千兆字节的ASCII文本数据文件,每行有一个键值对。该文件共包含3339550320行,无法在我尝试过的任何编辑器中打开,包括使用Vim。我需要对这个文件进行子集,以便调查我发现的一些值,这些值仅从第~500000000行开始。
因为文件有很多行:
我只需要提取行的一个子集,就可以对数据进行任何有用的操作。通读每一行,得出我所关心的值,需要很长时间。如果解决方案读取了我关心的行,并继续读取文件的其余部分,那么将浪费时间读取近30亿个不相关的行,所需时间将比需要的时间长6倍。
我最好的方案是只从文件中提取一行,而不读取文件中的任何其他行,但我想不出如何在Bash中实现这一点。
为了我的理智,我不会试图阅读我自己的问题所需要的全部500000000行。相反,我将尝试从3339550320中提取第50000000行(这意味着读取整个文件需要比所需时间长60倍)。
我将使用内置的时间对每个命令进行基准测试。
基线
首先,让我们看看头尾解决方案是如何实现的:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
5000万行的基线是00:01:15.321,如果我直冲5亿行,大概需要12.5分钟。
cut
我对这一点半信半疑,但值得一试:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
这只跑了00:05:12.156,比基线慢得多!我不确定它是在停止之前读取整个文件还是仅读取5000万行,但无论如何,这似乎不是解决问题的可行方案。
AWK
我只使用出口运行解决方案,因为我不打算等待完整文件运行:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
这段代码运行时间为00:01:16.583,仅慢了约1秒,但与基线相比仍没有改善。按照这个速度,如果退出命令被排除,那么读取整个文件可能需要大约76分钟!
Perl
我还运行了现有的Perl解决方案:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
该代码在00:01:13.146运行,比基线快了约2秒。如果我用5000万美元来运行它,可能需要大约12分钟。
sed
上面的答案是我的结果:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
这段代码以00:01:12.705运行,比基线快3秒,比Perl快0.4秒。如果我在整个500000000行上运行它,可能需要大约12分钟。
映射文件
我有bash 3.1,因此无法测试mapfile解决方案。
结论
看起来,在大多数情况下,很难改进头尾解决方案。最好情况下,sed解决方案可提高约3%的效率。
(使用公式%=(运行时/基线-1)*100计算的百分比)
第50000000行
00:01:12.705(-00:00:02.616=-3.47%)秒00:01:13.146(00:00:02.175=-2.89%)perl00:01:15.321(+00:00:00.000=+0.00%)头部|尾部00:01:16.583(+00:00:01.262=+1.68%)awk00:05:12.156(+000:03:56.835=+314.43%)切割
第500000000行
00:12:07.050(-00:00:26.160)秒00:12:11.460(-00:00:21.750)佩尔00:12:33.210(+00:00:00.000)头|尾00:12:45.830(+00:00:12.620)awk00:52:01.560(+00:40:31.650)切割
行3338559320
01:20:54.599(-00:03:05.327)秒01:21:24.045(-00:02:25.227)佩尔01:23:49.273(+00:00:00.000)头|尾01:25:13.548(+000:02:35.735)awk05:47:23.026(+04:24:26.246)切割
有了awk,速度相当快:
awk 'NR == num_line' file
如果为true,则执行awk的默认行为:{print$0}。
替代版本
如果您的文件恰好很大,最好在读取所需的行后退出。这样可以节省CPU时间请参见答案末尾的时间比较。
awk 'NR == num_line {print; exit}' file
如果要从bash变量中给出行号,可以使用:
awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file # equivalent
查看使用exit节省了多少时间,特别是如果该行恰好位于文件的第一部分:
# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines
$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla
real 0m1.303s
user 0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla
real 0m0.198s
user 0m0.178s
sys 0m0.013s
因此,两者的差异是0.198秒对1.303秒,大约快了6倍。
已经有很多好答案了。我个人喜欢awk。为了方便起见,如果您使用bash,只需将以下内容添加到~/.bash_profile中即可。下次登录时(或者如果您在本次更新后获取.bash_profile的源代码),您将有一个新的漂亮的“第n”函数可用于管道传输文件。
执行此命令或将其放入~/.bash_profile(如果使用bash)并重新打开bash(或执行源~/.bach_profile)
# print just the nth piped in line
nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
然后,要使用它,只需通过管道。例如:
$ yes line | cat -n | nth 5
5 line
sed -n '2p' < file.txt
将打印第二行
sed -n '2011p' < file.txt
2011线
sed -n '10,33p' < file.txt
第10行到第33行
sed -n '1p;3p' < file.txt
第1和第3行
等等
对于使用sed添加行,您可以选中此项:
sed:在某个位置插入一行