cat line X 到 line Y 在一个大文件上

Ame*_*ina 171 large-files tail cat head

说我有一个巨大的文本文件(> 2GB),我只是想catXY(如57890000至57890010)。

据我所知,我可以通过管道head输入tail或反之亦然来做到这一点,即

head -A /path/to/file | tail -B
Run Code Online (Sandbox Code Playgroud)

或者

tail -C /path/to/file | head -D
Run Code Online (Sandbox Code Playgroud)

其中ABCD可以根据文件中的行数计算得出,XY

但是这种方法有两个问题:

  1. 你必须计算ABCD
  2. 这些命令可以pipe相互发送比我感兴趣的多得多的行(例如,如果我只读取大文件中间的几行)

有没有办法让 shell 只使用并输出我想要的行?(同时仅提供XY)?

Kev*_*vin 152

我建议sed解决方案,但为了完整起见,

awk 'NR >= 57890000 && NR <= 57890010' /path/to/file
Run Code Online (Sandbox Code Playgroud)

在最后一行之后剪掉:

awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file
Run Code Online (Sandbox Code Playgroud)

速度测试(在 macOS 上,YMMV 在其他系统上):

  • 100,000,000 行文件由 seq 100000000 > test.in
  • 阅读行 50,000,000-50,000,010
  • 没有特定顺序的测试
  • realbash的内置报告的时间time
 4.373  4.418  4.395    tail -n+50000000 test.in | head -n10
 5.210  5.179  6.181    sed -n '50000000,50000010p;57890010q' test.in
 5.525  5.475  5.488    head -n50000010 test.in | tail -n10
 8.497  8.352  8.438    sed -n '50000000,50000010p' test.in
22.826 23.154 23.195    tail -n50000001 test.in | head -n10
25.694 25.908 27.638    ed -s test.in <<<"50000000,50000010p"
31.348 28.140 30.574    awk 'NR<57890000{next}1;NR==57890010{exit}' test.in
51.359 50.919 51.127    awk 'NR >= 57890000 && NR <= 57890010' test.in
Run Code Online (Sandbox Code Playgroud)

这些绝不是精确的基准测试,但差异很明显且可重复*,可以很好地了解每个命令的相对速度。

*:除了前两个sed -n p;qhead|tail,它们似乎本质上是一样的。

  • 出于好奇:您是如何在测试之间刷新磁盘缓存的? (13认同)
  • 好的,我去做了一些基准测试。tail|head 比 sed 快得多,差异比我预期的要大得多。 (6认同)
  • @Gilles 你是对的,我的错。`tail+|head` 比 sed 快 10-15%,我已经添加了该基准。 (3认同)
  • `tail -n +50000000 test.in 怎么样?head -n10`,它不同于`tail -n-50000000 test.in | head -n10` 会给出正确的结果吗? (2认同)

Gil*_*il' 68

如果您想要包含 X 到 Y 的行(从 1 开始编号),请使用

tail -n "+$X" /path/to/file | head -n "$((Y-X+1))"
Run Code Online (Sandbox Code Playgroud)

tail将读取并丢弃前 X-1 行(没有办法解决),然后读取并打印以下行。head将读取并打印请求的行数,然后退出。当head退出时,tail接收SIGPIPE信号和模具,因此它不会比从输入文件中的行缓冲器的大小的值(通常是几千字节)更多。

或者,按照gorkypl 的建议,使用 sed:

sed -n -e "$X,$Y p" -e "$Y q" /path/to/file
Run Code Online (Sandbox Code Playgroud)

但是 sed 解决方案要慢得多(至少对于 GNU 实用程序和 Busybox 实用程序;如果您在管道缓慢而 sed 快速的操作系统上提取大部分文件,sed 可能更具竞争力)。以下是 Linux 下的快速基准测试;数据由 生成seq 100000000 >/tmp/a,环境为 Linux/amd64,/tmp为 tmpfs,否则机器空闲且不交换。

real  user  sys    command
 0.47  0.32  0.12  </tmp/a tail -n +50000001 | head -n 10 #GNU
 0.86  0.64  0.21  </tmp/a tail -n +50000001 | head -n 10 #BusyBox
 3.57  3.41  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #GNU
11.91 11.68  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #BusyBox
 1.04  0.60  0.46  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #GNU
 7.12  6.58  0.55  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #BusyBox
 9.95  9.54  0.28  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #GNU
23.76 23.13  0.31  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #BusyBox
Run Code Online (Sandbox Code Playgroud)

如果您知道要使用的字节范围,则可以通过直接跳到开始位置来更快地提取它。但是对于行,您必须从头开始阅读并计算换行符。从 0 开始从 x 包含块到 y 不包含块提取块,块大小为 b:

dd bs="$b" seek="$x" count="$((y-x))" </path/to/file
Run Code Online (Sandbox Code Playgroud)

  • @Gilles 当从末尾给出行数时,似乎可以避免“tail 将读取并丢弃第一个 X-1 行”,在这种情况下,tail 似乎根据执行时间从末尾向后读取。请阅读:`http://unix.stackexchange.com/a/216614/79743`。 (2认同)

jw0*_*013 29

head | tail方法是执行此操作的最佳和最“惯用”方法之一:

X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"
Run Code Online (Sandbox Code Playgroud)

正如吉尔斯在评论中指出的那样,更快的方法是

< infile.txt tail -n +"$X" | head -n "$((Y - X))"
Run Code Online (Sandbox Code Playgroud)

这样做更快的原因是与方法相比,第一条X-1线不需要穿过管道head | tail

你的问题措辞有点误导,可能解释了你对这种方法的一些毫无根据的疑虑。

  • 你说你必须计算A, B, CD但正如你所看到的,不需要文件的行数,最多需要 1 次计算,shell 无论如何都可以为你做。

  • 您担心管道会读取不必要的行数。事实上,事实并非如此:tail | head就文件 I/O 而言,它的效率与您所能获得的效率差不多。首先,考虑所需的最少工作量:要找到文件中的第X行,唯一的通用方法是读取每个字节并在计算X 个换行符时停止,因为无法确定文件第X行的偏移量。到达第 *X* 行后,您必须阅读所有行才能打印它们,停在第Y行。因此,没有任何方法可以避免读取少于Y行的内容。现在,head -n $Y读取不超过Y行(四舍五入到最近的缓冲单元,但如果正确使用缓冲区可以提高性能,因此无需担心该开销)。此外,tail不会读取超过head,因此我们已经证明head | tail读取的行数可能最少(同样,加上一些我们忽略的可忽略缓冲)。不使用管道的单一工具方法的唯一效率优势是更少的进程(因此开销更少)。

  • 以前从未见过重定向首先上线。酷,它使管道流动更清晰。 (3认同)

Paw*_*ian 20

最正统的方法(但不是最快的,正如上面Gilles所指出的)是使用sed.

在你的情况下:

X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename
Run Code Online (Sandbox Code Playgroud)

-n选项意味着只有相关的行被打印到标准输出。

结束行号末尾的p表示在给定范围内打印行。该q在脚本的第二部分通过跳过文件的剩余部分可以节省一些时间。


小智 7

如果我们知道要选择的范围,从第一行:lStart到最后一行:lEnd我们可以计算:

lCount="$((lEnd-lStart+1))"
Run Code Online (Sandbox Code Playgroud)

如果我们知道总行数:lAll我们还可以计算到文件末尾的距离:

toEnd="$((lAll-lStart+1))"
Run Code Online (Sandbox Code Playgroud)

然后我们就会知道:

"how far from the start"            ($lStart) and
"how far from the end of the file"  ($toEnd).
Run Code Online (Sandbox Code Playgroud)

选择其中最小的一个:tailnumber如下:

tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"
Run Code Online (Sandbox Code Playgroud)

允许我们使用一贯最快的执行命令:

tail -n"${tailnumber}" ${thefile} | head -n${lCount}
Run Code Online (Sandbox Code Playgroud)

请注意选择时的附加加号 (“+”) $linestart

唯一需要注意的是我们需要总行数,这可能需要一些额外的时间才能找到。
像往常一样:

linesall="$(wc -l < "$thefile" )"
Run Code Online (Sandbox Code Playgroud)

测量的一些时间是:

lStart |500| lEnd |500| lCount |11|
real   user   sys    frac
0.002  0.000  0.000  0.00  | command == tail -n"+500" test.in | head -n1
0.002  0.000  0.000  0.00  | command == tail -n+500 test.in | head -n1
3.230  2.520  0.700  99.68 | command == tail -n99999501 test.in | head -n1
0.001  0.000  0.000  0.00  | command == head -n500 test.in | tail -n1
0.001  0.000  0.000  0.00  | command == sed -n -e "500,500p;500q" test.in
0.002  0.000  0.000  0.00  | command == awk 'NR<'500'{next}1;NR=='500'{exit}' test.in


lStart |50000000| lEnd |50000010| lCount |11|
real   user   sys    frac
0.977  0.644  0.328  99.50 | command == tail -n"+50000000" test.in | head -n11
1.069  0.756  0.308  99.58 | command == tail -n+50000000 test.in | head -n11
1.823  1.512  0.308  99.85 | command == tail -n50000001 test.in | head -n11
1.950  2.396  1.284  188.77| command == head -n50000010 test.in | tail -n11
5.477  5.116  0.348  99.76 | command == sed -n -e "50000000,50000010p;50000010q" test.in
10.124  9.669  0.448  99.92| command == awk 'NR<'50000000'{next}1;NR=='50000010'{exit}' test.in


lStart |99999000| lEnd |99999010| lCount |11|
real   user   sys    frac
0.001  0.000  0.000  0.00  | command == tail -n"1001" test.in | head -n11
1.960  1.292  0.660  99.61 | command == tail -n+99999000 test.in | head -n11
0.001  0.000  0.000  0.00  | command == tail -n1001 test.in | head -n11
4.043  4.704  2.704  183.25| command == head -n99999010 test.in | tail -n11
10.346  9.641  0.692  99.88| command == sed -n -e "99999000,99999010p;99999010q" test.in
21.653  20.873  0.744  99.83 | command == awk 'NR<'99999000'{next}1;NR=='99999010'{exit}' test.in
Run Code Online (Sandbox Code Playgroud)

请注意,如果所选行靠近起点或终点,则时间会发生巨大变化。一个在文件一侧运行良好的命令,在文件的另一侧可能非常慢。