有没有一种“规范”的方法?我一直在使用head-n|tail-1来完成这个任务,但我一直在想是否有一个Bash工具专门从文件中提取一行(或一系列行)。

我所说的“规范”是指一个主要功能就是这样做的程序。


当前回答

我有一个独特的情况,我可以对本页上提出的解决方案进行基准测试,因此我将此答案作为所提出解决方案的合并,并包含每个解决方案的运行时间。

设置

我有一个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)切割

其他回答

大文件的最快解决方案始终是尾部|头部,前提是两个距离:

从文件开头到开始行。我们称之为S从最后一行到文件结尾的距离。是E吗

是已知的。然后,我们可以使用这个:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

多少只是所需的行数。

更多详情请参见https://unix.stackexchange.com/a/216614/79743

在查看了顶部答案和基准之后,我实现了一个小助手函数:

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

基本上,您可以以两种方式使用它:

nth 42 myfile.txt
do_stuff | nth 42

获取第n行(单行)

如果您想要一些以后可以自定义而不必处理bash的东西,可以编译这个c程序,并将二进制文件放到您的自定义二进制文件目录中。这假设您知道如何编辑.bashrc文件相应地(仅当您想要编辑路径变量时),如果您不知道,这是一个有用的链接。

要运行此代码,请使用(假设您将二进制代码命名为“行”)。

line [target line] [target file]

实例

line 2 somefile.txt

代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[]){

  if(argc != 3){
      fprintf(stderr, "line needs a line number and a file name");
      exit(0);     
  }

  int lineNumber = atoi(argv[1]); 
  int counter = 0; 
  char *fileName = argv[2];

  FILE *fileReader = fopen(fileName, "r");
  if(fileReader == NULL){
      fprintf(stderr, "Failed to open file"); 
      exit(0); 
  }

  size_t lineSize = 0;
  char* line = NULL;

  while(counter < lineNumber){
     getline(&line, &linesize, fileReader);
     counter++
  }

  getline(&line, &lineSize, fileReader);

  printf("%s\n", line);     

  fclose(fileReader); 
  return 0; 
}

EDIT:删除fseek并用while循环替换它

UPDATE 1:在awk中找到了更快的方法

仅需5.353秒即可获得133.6 mn以上的行:

行号='133668997';(时间(pvE0<~/master_primelist_18a.txt|LC_ALL=C mawk2-F'^$'-v\_=“${rownum}”--'_{exit}!--_'))

in0: 5.45GiB 0:00:05 [1.02GiB/s] [1.02GiB/s] [======> ] 71%            
     ( pvE 0.1 in0 < ~/master_primelist_18a.txt | 
     LC_ALL=C mawk2 -F'^$' -v  -- ; )  5.01s user 

1.21s系统116%cpu 5.353总计

77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=

===============================================

我想质疑perl比awk更快的观点:

因此,虽然我的测试文件的行数没有那么多,但它的大小也是原来的两倍,为7.58GB-

我甚至给了perl一些内置的优势——比如行号中的硬编码,并且还排在第二位,从而从OS缓存机制中获得了任何潜在的加速(如果有的话)

 f="$( grealpath -ePq  ~/master_primelist_18a.txt )"
 rownum='133668997'
 fg;fg; pv < "${f}" | gwc -lcm 
 echo; sleep 2; 
 echo; 
 ( time ( pv -i 0.1 -cN in0 < "${f}" | 
        
    LC_ALL=C mawk2 '_{exit}_=NR==+__' FS='^$' __="${rownum}" 

 ) ) | mawk 'BEGIN { print } END { print _ } NR' 
 sleep 2
 ( time ( pv -i 0.1 -cN in0 < "${f}" | 

    LC_ALL=C perl -wnl -e '$.== 133668997 && print && exit;' 

 ) ) | mawk 'BEGIN { print }  END { print _ } NR' ;

fg: no current job
fg: no current job
7.58GiB 0:00:28 [ 275MiB/s] [============>] 100%
        
148,110,134 8,134,435,629 8,134,435,629   <<<< rows, chars, and bytes 
                                               count as reported by gnu-wc



      in0: 5.45GiB 0:00:07 [ 701MiB/s] [=> ] 71%            
( pv -i 0.1 -cN in0 < "${f}" | LC_ALL=C mawk2 '_{exit}_=NR==+__' FS='^$' ; )  
   6.22s user 2.56s system 110% cpu 7.966 total
   77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=


      in0: 5.45GiB 0:00:17 [ 328MiB/s] [=> ] 71%            
( pv -i 0.1 -cN in0 < "${f}" | LC_ALL=C perl -wnl -e ; )  
   14.22s user 3.31s system 103% cpu 17.014 total
   77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=

我可以用perl5.36甚至perl-6重新运行测试,如果你认为这会有所不同(也没有安装)

7.966秒(mawk2)与17.014秒(perl 5.34)

在这两者之间,后者是前者的两倍多,显然哪一种在ASCII文件中获取单行数据确实更快。

This is perl 5, version 34, subversion 0 (v5.34.0) built for darwin-thread-multi-2level

Copyright 1987-2021, Larry Wall


mawk 1.9.9.6, 21 Aug 2016, Copyright Michael D. Brennan

您也可以使用Perl实现这一点:

perl -wnl -e '$.== NUM && print && exit;' some.file