Bash工具从文件中获得第n行
有没有一个“规范”的方式呢? 我一直在使用head -n | tail -1
head -n | tail -1
这个技巧,但我一直想知道是否有一个Bash工具,专门从文件中提取一行(或一系列的行)。
“规范”是指一个主要function就是这样的程序。
一个巨大的文件head
和pipetail
会慢。 我会build议像这样sed
:
sed 'NUMq;d' file
其中NUM
是要打印的行数; 所以,例如, sed '10q;d' file
打印sed '10q;d' file
的第10行。
说明:
当行号是NUM
时, NUMq
将立即退出。
d
将删除行而不是打印它; 这在最后一行被禁止,因为q
导致脚本的其余部分在退出时被跳过。
如果你在一个variables中有NUM
,你将会使用双引号而不是单引号:
sed "${NUM}q;d" file
sed -n '2p' < file.txt
将打印第二行
sed -n '2011p' < file.txt
2011th线
sed -n '10,33p' < file.txt
第10行到第33行
sed -n '1p;3p' < file.txt
第一和第三行
等等…
用sed添加行,你可以检查:
sed:在某个位置插入一条线
awk
速度非常快:
awk 'NR == num_line' file
如果这是真的,则执行awk
的默认行为: {print $0}
。
替代版本
如果你的文件很大,你最好在阅读完所需的行后exit
。 这样可以节省CPU时间。
awk 'NR == num_line {print; exit}' file
如果你想给一个bashvariables的行号,你可以使用:
awk 'NR == n' n=$num file awk -vn=$num 'NR == n' file # equivalent
我有一个独特的情况,我可以在本页面提出的解决scheme基准,所以我写这个答案作为提出的解决scheme的合并与每个包括运行时间。
build立
我有一个3.261千兆字节的ASCII文本数据文件,每行一个键值对。 该文件总共包含3,339,550,320行,并且在我尝试过的任何编辑器(包括我的前往Vim)中都是无法打开的。 我需要对这个文件进行子集分析,以便调查我发现的一些值只能在〜500,000,000行左右开始。
由于该文件有很多行:
- 我只需要提取行的一个子集来做任何有用的数据。
- 通过阅读每一行导致我关心的价值观将需要很长时间。
- 如果解决scheme读取我关心的行并继续读取文件的其余部分,则将浪费时间读取近30亿个不相关的行,并且花费比所需的多6倍的时间。
我最好的情况是从文件中只提取一行,而不读取文件中的任何其他行的解决scheme,但我想不出在Bash中如何实现这一点。
出于我的理智的目的,我不会试图去阅读我自己的问题需要的全部5亿条线。 相反,我将尝试从3,339,550,320行中提取50,000,000行(这意味着读取完整文件将比所需的长60倍)。
我将使用内置的time
来对每个命令进行基准testing。
底线
首先让我们看看tail
解决scheme:
$ time head -50000000 myfile.ascii | tail -1 pgm_icnt = 0 real 1m15.321s
5000万行的基线是00:01:15.321,如果我直奔5亿行,大概是12.5分钟。
切
我很怀疑这个,但值得一试:
$ time cut -f50000000 -d$'\n' myfile.ascii pgm_icnt = 0 real 5m12.156s
这一个花了00:05:12.156跑,这比基线慢得多! 我不确定是否通读整个文件,或者在停止之前达到5000万行,但不pipe这个问题是不是一个可行的解决scheme。
AWK
我只是用exit
运行解决scheme,因为我不打算等待完整的文件运行:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii pgm_icnt = 0 real 1m16.583s
这个代码运行在00:01:16.583,这只是慢了1秒,但仍然没有改善基线。 按照这个速度,如果退出命令已被排除,那么读取整个文件大概需要大约76分钟!
Perl的
我也运行了现有的Perl解决scheme:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii pgm_icnt = 0 real 1m13.146s
这段代码运行在00:01:13.146,比基线快了2秒。 如果我在5亿美元上运行它大概需要12分钟。
SED
在董事会的最佳答案,这是我的结果:
$ time sed "50000000q;d" myfile.ascii pgm_icnt = 0 real 1m12.705s
这段代码运行在00:01:12.705,比基线快3秒,比Perl快0.4秒。 如果我在5亿行上运行它大概需要12分钟。
映射文件
我有bash 3.1,因此无法testingmapfile解决scheme。
结论
看起来大多数情况下,很难改善tail
解决scheme。 sed
解决scheme最多可提供约3%的效率提升。
(以公式% = (runtime/baseline - 1) * 100
计算的百分比)
行50,000,000
- 00:01:12.705(-00:00:02.616 = -3.47%)
sed
- 00:01:13.146(-00:00:02.175 = -2.89%)
perl
- 00:01:15.321(+00:00:00.000 = + 0.00%)
- 00:01:16.583(+00:00:01.262 = + 1.68%)
awk
- 00:05:12.156(+00:03:56.835 = + 314.43%)
行500,000,000
- 00:12:07.050(-00:00:26.160)
sed
- 00:12:11.460(-00:00:21.750)
perl
- 00:12:33.210(+00:00:00.000)
head|tail
- 00:12:45.830(+00:00:12.620)
awk
- 00:52:01.560(+00:40:31.650)
行3,338,559,320
- 01:20:54.599(-00:03:05.327)
sed
- 01:21:24.045(-00:02:25.227)
perl
- 01:23:49.273(+00:00:00.000)
head|tail
- 01:25:13.548(+00:02:35.735)
awk
- 05:47:23.026(+04:24:26.246)
哇,所有的可能性!
尝试这个:
sed -n "${lineNum}p" $file
或者其中的一个取决于你的Awk版本:
awk -vlineNum=$lineNum 'NR == lineNum {print $0}' $file awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file
( 您可能需要尝试nawk
或gawk
命令 )。
有没有一种工具只能打印那一行? 没有一个标准的工具。 但是, sed
可能是最接近和最简单的使用。
# print line number 52 sed '52!d' file
sed有用的单行脚本
这个问题被标记为Bash,这里是Bash(≥4)的做法:使用带有-s
(跳过)和-n
(count)选项的mapfile
。
如果您需要获取文件file
的第42行:
mapfile -s 41 -n 1 ary < file
在这一点上,你将有一个数组,其中包含file
行(包括尾随的换行符),其中我们已经跳过了前41行( -s 41
),并在读取一行后停止( -n 1
)。 所以这真的是第42条线。 打印出来:
printf '%s' "${ary[0]}"
如果你需要一系列的行,比如说范围在42-666(含),并且说你不想自己做math,然后在stdout上打印它们:
mapfile -s $((42-1)) -n $((666-42+1)) ary < file printf '%s' "${ary[@]}"
如果你也需要处理这些行,那么存储尾随的换行符并不是很方便。 在这种情况下,使用-t
选项(trim):
mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file # do stuff printf '%s\n' "${ary[@]}"
你可以有一个function为你做这个:
print_file_range() { # $1-$2 is the range of file $3 to be printed to stdout local ary mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3" printf '%s' "${ary[@]}" }
没有外部命令,只有Bash内置的!
您也可以使用sed打印并退出:
sed -n '10{p;q;}' file # print line 10
你也可以使用Perl来完成这个工作:
perl -wnl -e '$.== NUM && print && exit;' some.file
对于大文件,最快的解决scheme始终是最终的,只要两个距离:
- 从文件开始到起始行。 让我们称它
S
- 从最后一行到文件结尾的距离。 是
E
已知。 那么,我们可以使用这个:
mycount="$E"; (( E > S )) && mycount="+$S" howmany="$(( endline - startline + 1 ))" tail -n "$mycount"| head -n "$howmany"
howmany只是需要的行数。
我会说这个head -n | tail -1
head -n | tail -1
很难被击败。 对我而言,这仍然是最好的解决scheme。
它是可移植的和相当可读的。 这也是非常快的。 其他答案包括一些基准,但是在你testing的系统上似乎有很大差异。
在我自己的(非代表性的)testing中,头部/尾巴一直胜过sed 'NUMq;d'
(速度明显更快)。 但是,即使在其他的基准testing中,也很难find头尾很糟的情况。 这也不足为奇,因为这些操作是您希望在现代Unix系统中大量优化的操作。
得票最高sed 'NUMq;d'
很有意思,但是我认为可以通过开箱即用的方式理解头部/尾部解决scheme。
可能的方法之一:
sed -n 'NUM{p;q}'
请注意,没有q
命令,如果文件很大,sed将继续工作,这会减慢计算速度。
要使用sed打印第n行,并将variables作为行号:
a=4 sed -e $a'q:d' file
这里的'-e'标志是为了执行命令添加脚本。
如果您通过\ n(通常为新行)分隔多行。 你也可以使用'cut'
echo "$data" | cut -f2 -d$'\n'
您将从文件中获得第二行。 -f3
给你第三行。
以上所有答案直接回答了这个问题。 但是,这是一个不那么直接的解决scheme,而是一个潜在的更重要的想法,引发思想。
由于行长度是任意的,因此需要读取第n行之前的文件的所有字节。 如果您的文件很大,或者需要多次重复执行此任务,而且这个过程非常耗时,那么您应该认真考虑是否应该以不同的方式存储您的数据。
真正的解决scheme是在文件的开始处有一个索引,指示行开始的位置。 您可以使用数据库格式,或者只是在文件的开头添加一个表格。 或者,创build一个单独的索引文件以伴随您的大型文本文件。
例如你可以为换行符创build一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
然后用tail
读,这实际上是直接find文件中的适当的点!
例如获得行1000:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
- 这可能不适用于2字节/多字节字符,因为awk是“字符意识”,但尾巴不是。
- 我没有testing过这个大文件。
- 也看到这个答案 。
- 或者 – 将文件分割成更小的文件!