Bash tool to get nth line from a file
有没有一种"规范"的方法来做到这一点?我一直在使用
"规范"是指一个程序,它的主要功能就是这样做。
管与
1 | sed 'NUMq;d' file |
在
解释:
如果你有一个在
1 | sed"${NUM}q;d" file |
1 | sed -n '2p' < file.txt |
第二,将打印线
1 | sed -n '2011p' < file.txt |
2011th线
1 | sed -n '10,33p' < file.txt |
线到线10 33
1 | sed -n '1p;3p' < file.txt |
线1和3
在线等…………………
如果你可以将线与检查:对话,这
对话:插入线在一定的位置
我有一个独特的情况,我可以在这个页面上对建议的解决方案进行基准测试,所以我写这个答案是将建议的解决方案与每个解决方案包含的运行时间合并在一起。
设置
我有一个3.261千兆字节的ASCII文本数据文件,每行一个键值对。该文件总共包含3339550320行,并且拒绝在我尝试过的任何编辑器中打开,包括我的go to vim。我需要对这个文件进行子集,以便调查我发现的一些值,这些值只从大约500000000行开始。
因为文件有这么多行:
- 我只需要提取行的一个子集,就可以对数据进行任何有用的操作。
- 阅读每一行我关心的价值观都需要很长时间。
- 如果解决方案读取的内容超过了我关心的行,并且继续读取文件的其余部分,那么它将浪费时间读取近30亿个无关行,并比需要的时间长6倍。
我的最佳方案是在不读取文件中任何其他行的情况下,只从文件中提取一行,但我想不出如何在bash中完成这一点。
为了我的理智,我不会去阅读我自己的问题所需要的全部5亿行。相反,我将尝试从3339550320中提取第50000000行(这意味着读取完整文件将比需要的时间长60倍)。
我将使用
基线
首先,让我们看看
1 2 3 4 | $ time head -50000000 myfile.ascii | tail -1 pgm_icnt = 0 real 1m15.321s |
第5000万行的基线是00:01:15.321,如果我直走到第5亿行,大概是12.5分钟。
切
我对这件事很怀疑,但值得一试:
1 2 3 4 5 | $ time cut -f50000000 -d$' ' myfile.ascii pgm_icnt = 0 real 5m12.156s |
这一次用了00:05:12.156跑,比基线慢得多!我不确定它是读取整个文件,还是在停止前读取多达5000万行,但无论如何,这似乎不是解决问题的可行方案。
AWK
我只使用
1 2 3 4 | $ time awk 'NR == 50000000 {print; exit}' myfile.ascii pgm_icnt = 0 real 1m16.583s |
这段代码以00:01:16.583的速度运行,只慢了约1秒,但仍然没有改善基线。按照这个速度,如果exit命令被排除在外,读取整个文件可能需要大约76分钟!
珀尔
我也运行了现有的Perl解决方案:
1 2 3 4 | $ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii pgm_icnt = 0 real 1m13.146s |
此代码以00:01:13.146运行,比基线快约2秒。如果我能以5亿美元的速度运行,大概需要12分钟。
塞德
最重要的答案是:
1 2 3 4 | $ 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,因此无法测试mapfile解决方案。
结论
在大多数情况下,似乎难以改进
(用公式
第50000000行
第500000000行
世界其他地区3338559320
它是快速和
1 | awk 'NR == num_line' file |
当这是真的,默认的行为是:
替代版本
如果你的文件发生的是巨大的,你最好
1 | awk 'NR == num_line {print; exit}' file |
如果你想给线数从shell变量,你可以使用:
1 2 | awk 'NR == n' n=$num file awk -v n=$num 'NR == n' file # equivalent |
哇,所有的可能性!
试试这个:
1 | sed -n"${lineNum}p" $file |
在一个或这些取决于你的awk版本:
1 2 3 | 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 |
(你可能要尝试的
有工具,特别是只做打印线?没有一个标准的工具。然而,最近的
1 2 | # print line number 52 sed '52!d' file |
有用的sed脚本是一个地图
这个问题被标记在bash Bash的方式做(4):使用与
如果你需要把这个文件
1 | mapfile -s 41 -n 1 ary < file |
在这一点上,你有一个阵列的场
1 | printf '%s'"${ary[0]}" |
如果你需要的范围线,42–666说的范围(包括)和说,你不想你的数学,他们的在线和打印输出:
1 2 | mapfile -s $((42-1)) -n $((666-42+1)) ary < file printf '%s'"${ary[@]}" |
如果你需要这些线的过程也不是很方便,到店后换行符。在这个案例的使用
1 2 3 4 | mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file # do stuff printf '%s '"${ary[@]}" |
你可以有你的函数是:
1 2 3 4 5 6 | 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打印和退出:
1 | sed -n '10{p;q;}' file # print line 10 |
根据我的测试,在性能和可读性方面,我的建议是:
替代的
在性能方面,较小的文件大小没有太大的差别,但当文件变得巨大时,它会比
最有投票权的
在我的测试中,两个tail/heads版本始终优于
为了了解性能差异,这些是我从一个大文件(9.3g)中得到的数字:
tail -n+N | head -1 3.7秒head -N | tail -1 :4.6秒sed Nq;d 18.8秒
结果可能会有所不同,但一般来说,
要复制我的基准测试,您可以尝试以下操作,但请注意,它将在当前工作目录中创建一个9.3g文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #!/bin/bash readonly file=tmp-input.txt readonly size=1000000000 readonly pos=500000000 readonly retries=3 seq 1 $size > $file echo"*** head -N | tail -1 ***" for i in $(seq 1 $retries) ; do time head"-$pos" $file | tail -1 done echo"-------------------------" echo echo"*** tail -n+N | head -1 ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time tail -n+$pos $file | head -1 done echo"-------------------------" echo echo"*** sed Nq;d ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time sed $pos'q;d' $file done /bin/rm $file |
这是在我的机器上运行的输出(ThinkPad x1 Carbon,带有一个SSD和16G内存)。我假设在最后一次运行中,所有内容都将来自缓存,而不是磁盘:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | *** head -N | tail -1 *** 500000000 real 0m9,800s user 0m7,328s sys 0m4,081s 500000000 real 0m4,231s user 0m5,415s sys 0m2,789s 500000000 real 0m4,636s user 0m5,935s sys 0m2,684s ------------------------- *** tail -n+N | head -1 *** -rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt 500000000 real 0m6,452s user 0m3,367s sys 0m1,498s 500000000 real 0m3,890s user 0m2,921s sys 0m0,952s 500000000 real 0m3,763s user 0m3,004s sys 0m0,760s ------------------------- *** sed Nq;d *** -rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt 500000000 real 0m23,675s user 0m21,557s sys 0m1,523s 500000000 real 0m20,328s user 0m18,971s sys 0m1,308s 500000000 real 0m19,835s user 0m18,830s sys 0m1,004s |
你也可以使用Perl是这样的:
1 | perl -wnl -e '$.== NUM && print && exit;' some.file |
该文件是最快的解决方案总是头大,尾|,提供两个距离:
- 从文件的开始的起点。我们呼叫它
S - 该线的距离到最后结束的文件。它是
E
是已知的。然后,我们可以使用这个。
1 2 3 | mycount="$E"; (( E > S )) && mycount="+$S" howmany="$(( endline - startline + 1 ))" tail -n"$mycount"| head -n"$howmany" |
你是一只howmany线的需要。
更多细节将在unix.stackexchange.com https://///79743 216614
以上答案都直接回答问题。但这里有一个不那么直接的解决方案,但有一个潜在的更重要的想法,来激发人们的想法。
由于行长度是任意的,所以需要读取第n行之前文件的所有字节。如果您有一个巨大的文件,或者需要多次重复此任务,并且此过程非常耗时,那么您首先应该认真考虑是否应该以不同的方式存储数据。
真正的解决方案是有一个索引,例如在文件的开头,指示行开始的位置。您可以使用数据库格式,也可以在文件开头添加一个表。或者创建一个单独的索引文件,与大文本文件一起使用。
例如,可以为换行创建字符位置列表:
1 | awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx |
然后用
例如,要获取第1000行:
1 | tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1 |
- 这可能不适用于2字节/多字节字符,因为awk是"字符感知的",但tail不是。
- 我还没有用一个大文件测试过这个。
- 也可以看到这个答案。
- 或者-将文件拆分为较小的文件!
作为对咖啡馆非常有帮助的基准答案的跟进…我很好奇"mapfile"方法与其他方法相比有多快(因为它没有经过测试),所以我自己尝试了一个快速而肮脏的速度比较,因为我有bash 4。当我在上面的答案上发表评论的时候,在人们唱赞歌的时候,对上面提到的"尾头"方法(而不是头尾)进行了测试。我没有使用的测试文件大小的任何东西;我能在短时间内发现的最好的是一个14M的系谱文件(长的行是空格分隔的,小于12000行)。
简短的版本:mapfile看起来比cut方法快,但比其他方法慢,所以我称它为dud。尾头,奥托,看起来它可能是最快的,尽管有了这个大小的文件,与SED相比差异并不大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | $ time head -11000 [filename] | tail -1 [output redacted] real 0m0.117s $ time cut -f11000 -d$' ' [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(新线)。你可以使用"切"的目的。
1 2 | echo"$data" | cut -f2 -d$' ' |
你会得到从第二行的文件。你
已经有很多好的答案了。我个人和awk一起去。为了方便起见,如果您使用bash,只需将下面的内容添加到您的
执行此操作或将其放入~/.bash_配置文件(如果使用bash)并重新打开bash(或执行
nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
然后,要使用它,只需通过管道。例如。,:
5 line
使用其他人提到的内容,我希望这在我的bash shell中是一个快速而漂亮的函数。
创建文件:
添加内容:
getline() {
line=$1
sed $line'q;d' $2
}
然后将此添加到您的
现在,当您打开一个新的bash窗口时,您可以这样调用函数:
n采用SED与印刷线的线号:可变
1 2 | a=4 sed -e $a'q:d' file |
这里的"-"标志是添加到脚本的命令被执行。
我把上面的一些答案放到一个简短的bash脚本中,你可以把它放到一个名为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/bin/bash if ["${1}" =="" ]; then echo"error: blank line number"; exit 1 fi re='^[0-9]+$' if ! [[ $1 =~ $re ]] ; then echo"error: line number arg not a number"; exit 1 fi if ["${2}" =="" ]; then echo"error: blank file name"; exit 1 fi sed"${1}q;d" $2; exit 0 |
确保它是可执行的
1 | $ chmod +x get |
链接它,使其在
1 | $ ln -s get.sh /usr/local/bin/get |
负责任地享受吧!
磷