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

  1. 00:01:12.705(-00:00:02.616 = -3.47%) sed
  2. 00:01:13.146(-00:00:02.175 = -2.89%) perl
  3. 00:01:15.321(+00:00:00.000 = + 0.00%)
  4. 00:01:16.583(+00:00:01.262 = + 1.68%) awk
  5. 00:05:12.156(+00:03:56.835 = + 314.43%)

行500,000,000

  1. 00:12:07.050(-00:00:26.160) sed
  2. 00:12:11.460(-00:00:21.750) perl
  3. 00:12:33.210(+00:00:00.000) head|tail
  4. 00:12:45.830(+00:00:12.620) awk
  5. 00:52:01.560(+00:40:31.650)

行3,338,559,320

  1. 01:20:54.599(-00:03:05.327) sed
  2. 01:21:24.045(-00:02:25.227) perl
  3. 01:23:49.273(+00:00:00.000) head|tail
  4. 01:25:13.548(+00:02:35.735) awk
  5. 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 

您可能需要尝试nawkgawk命令 )。

有没有一种工具只能打印那一行? 没有一个标准的工具。 但是, 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只是需要的行数。

https://unix.stackexchange.com/a/216614/79743中的更多详细信息;

我会说这个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过这个大文件。
  • 也看到这个答案 。
  • 或者 – 将文件分割成更小的文件!