anu*_*ava 733
headtail对于一个巨大的文件,和管道将是缓慢的.我建议sed像这样:
sed 'NUMq;d' file
Run Code Online (Sandbox Code Playgroud)
NUM您要打印的行号在哪里; 所以,例如,sed '10q;d' file打印第10行file.
说明:
NUMq当行号为时,将立即退出NUM.
d将删除该行而不是打印它; 这在最后一行被禁止,因为q退出时会跳过脚本的其余部分.
如果你有NUM一个变量,你会想要使用双引号而不是单引号:
sed "${NUM}q;d" file
Run Code Online (Sandbox Code Playgroud)
jm6*_*666 278
sed -n '2p' < file.txt
Run Code Online (Sandbox Code Playgroud)
将打印第2行
sed -n '2011p' < file.txt
Run Code Online (Sandbox Code Playgroud)
2011年线
sed -n '10,33p' < file.txt
Run Code Online (Sandbox Code Playgroud)
第10行到第33行
sed -n '1p;3p' < file.txt
Run Code Online (Sandbox Code Playgroud)
第1和第3行
等等...
要使用sed添加行,您可以检查:
Caf*_*eur 84
我有一个独特的情况,我可以对本页提出的解决方案进行基准测试,因此我将这个答案写成所提议解决方案的合并,并包含每个解决方案的运行时间.
建立
我有一个3.261千兆字节的ASCII文本数据文件,每行有一个键值对.该文件总共包含3,339,550,320行,并且在我尝试过的任何编辑器中都无法打开,包括我的首选Vim.我需要对这个文件进行子集化,以便研究我发现的一些值,这些值只在行~500,000,000左右开始.
因为该文件有这么多行:
我最好的情况是一个解决方案,只从文件中提取一行而不读取文件中的任何其他行,但我想不出如何在Bash中实现这一点.
为了我的理智,我不会试图阅读我自己的问题所需的全部500,000,000行.相反,我将尝试从3,339,550,320中提取50,000,000行(这意味着读取完整文件所需的时间比所需时间长60倍).
我将使用time内置命令对每个命令进行基准测试.
底线
首先让我们看看head tail解决方案:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
Run Code Online (Sandbox Code Playgroud)
5000万行的基线是00:01:15.321,如果我直接行5亿行,它可能是~12.5分钟.
切
我怀疑这个,但它值得一试:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Run Code Online (Sandbox Code Playgroud)
这个运行时间为00:05:12.156,这比基线慢得多!我不确定它是在整个文件中读取还是在停止之前读取了5000万行,但无论如何这似乎都不是解决问题的可行办法.
AWK
我只运行了解决方案,exit因为我不打算等待运行完整文件:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Run Code Online (Sandbox Code Playgroud)
此代码运行在00:01:16.583,这只慢了约1秒,但仍然没有基线的改进.如果排除退出命令,这个速率可能需要大约76分钟来读取整个文件!
Perl的
我也运行了现有的Perl解决方案:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Run Code Online (Sandbox Code Playgroud)
此代码在00:01:13.146中运行,比基线快约2秒.如果我在500,000,000完全运行它可能需要大约12分钟.
SED
董事会的最佳答案,这是我的结果:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Run Code Online (Sandbox Code Playgroud)
此代码在00:01:12.705中运行,比基线快3秒,比Perl快〜0.4秒.如果我在完整的500,000,000行上运行它可能需要大约12分钟.
映射文件
我有bash 3.1,因此无法测试mapfile解决方案.
结论
看起来,在大多数情况下,很难改进head tail解决方案.最好的sed解决方案是效率提高约3%.
(用公式计算的百分比% = (runtime/baseline - 1) * 100)
排50,000,000
sedperlhead|tailawkcut排500,000,000
sedperlhead|tailawkcut第3,338,559,320行
sedperlhead|tailawkcutfed*_*qui 48
随着awk这是相当快:
awk 'NR == num_line' file
Run Code Online (Sandbox Code Playgroud)
如果是这样,awk则执行默认行为:{print $0}.
如果你的文件很大,那么exit在阅读完所需的行后你会更好.这样可以节省CPU时间.
awk 'NR == num_line {print; exit}' file
Run Code Online (Sandbox Code Playgroud)
如果要从bash变量中提供行号,可以使用:
awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file # equivalent
Run Code Online (Sandbox Code Playgroud)
Sau*_*ahu 33
节省两次击键,不使用括号打印第 N 行:
sed -n Np <fileName>
^ ^
\ \___ 'p' for printing
\______ '-n' for not printing by default
Run Code Online (Sandbox Code Playgroud)
例如,要打印第 100 行:
sed -n 100p foo.txt
Run Code Online (Sandbox Code Playgroud)
Dav*_* W. 26
哇,所有的可能性!
试试这个:
sed -n "${lineNum}p" $file
Run Code Online (Sandbox Code Playgroud)
或其中一个取决于您的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
Run Code Online (Sandbox Code Playgroud)
(您可能必须尝试nawk或gawk命令).
是否有工具只能打印特定的线?不是标准工具之一.但是,sed可能是最接近和最简单的使用方法.
Phi*_*ßen 22
根据我的测试,在性能和可读性方面,我的建议是:
tail -n+N | head -1
N是您想要的行号.例如,tail -n+7 input.txt | head -1将打印文件的第7行.
tail -n+N将从行开始打印所有内容N,并head -1在一行后停止.
替代方案head -N | tail -1可能稍微更具可读性.例如,这将打印第7行:
head -7 input.txt | tail -1
在性能方面,较小的尺寸没有太大区别,但是tail | head当文件变得庞大时,它会超越(从上面).
最高投票的人sed 'NUMq;d'很有兴趣知道,但我认为开箱即用的人数比头/尾解决方案更少,而且比尾部/头部慢.
在我的测试中,两个尾部/头部版本都表现出色sed 'NUMq;d'.这符合发布的其他基准.很难找到尾巴/头部非常糟糕的情况.这也就不足为奇了,因为这些操作可以在现代Unix系统中进行大量优化.
为了了解性能差异,这些是我获得的大型文件的数量(9.3G):
tail -n+N | head -1:3.7秒head -N | tail -1:4.6秒sed Nq;d:18.8秒结果可能不同,但性能head | tail和tail | head是,在一般情况下,对于较小的输入相媲美,而且sed始终是用显著因子(约5倍左右)更慢.
要重现我的基准测试,您可以尝试以下操作,但请注意它将在当前工作目录中创建一个9.3G文件:
#!/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
Run Code Online (Sandbox Code Playgroud)
这是我的机器上运行的输出(带有SSD和16G内存的ThinkPad X1 Carbon).我假设在最后一次运行中,一切都将来自缓存,而不是来自磁盘:
*** 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
Run Code Online (Sandbox Code Playgroud)
gni*_*urf 20
这个问题被标记猛砸,这里是干什么的巴什(≥4)的方式:用mapfile与-s(跳过)和-n(计数)选项.
如果你需要获取文件的第42行file:
mapfile -s 41 -n 1 ary < file
Run Code Online (Sandbox Code Playgroud)
此时,您将拥有一个数组ary,其中的字段包含file(包括尾部换行符)的行,我们跳过前41行(-s 41),并在读取一行(-n 1)后停止.所以这真的是第42行.打印出来:
printf '%s' "${ary[0]}"
Run Code Online (Sandbox Code Playgroud)
如果您需要一系列线条,比如42-666(含)范围,并说您不想自己做数学,并在标准输出上打印:
mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"
Run Code Online (Sandbox Code Playgroud)
如果你也需要处理这些行,那么存储尾随换行并不是很方便.在这种情况下使用-t选项(修剪):
mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"
Run Code Online (Sandbox Code Playgroud)
你可以有一个功能为你做这个:
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[@]}"
}
Run Code Online (Sandbox Code Playgroud)
没有外部命令,只有Bash内置!
小智 11
您也可以使用sed打印并退出:
sed -n '10{p;q;}' file # print line 10
Run Code Online (Sandbox Code Playgroud)
您也可以使用Perl:
perl -wnl -e '$.== NUM && print && exit;' some.file
Run Code Online (Sandbox Code Playgroud)
作为 CaffeineConnoisseur 非常有用的基准测试答案的后续……我很好奇“mapfile”方法与其他方法相比的速度有多快(因为它没有经过测试),所以我自己尝试了一个快速而肮脏的速度比较我有 bash 4 方便。当人们在赞美它时,对我在最佳答案的其中一条评论中提到的“tail | head”方法(而不是 head | tail)进行了测试。我没有任何与所使用的测试文件大小差不多的东西;我能在短时间内找到的最好的是一个 14M 的谱系文件(用空格分隔的长行,不到 12000 行)。
简短版本:mapfile 看起来比 cut 方法快,但比其他任何方法都慢,所以我称之为无用。尾巴 | 头,OTOH,看起来它可能是最快的,尽管与 sed 相比,对于这种大小的文件,差异并不是那么大。
$ time head -11000 [filename] | tail -1
[output redacted]
real 0m0.117s
$ time cut -f11000 -d$'\n' [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
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助!
小智 6
对于大文件最快的解决方案总是尾部,只要两个距离:
SE众所周知.然后,我们可以使用这个:
mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"
Run Code Online (Sandbox Code Playgroud)
howmany只是所需行数.
https://unix.stackexchange.com/a/216614/79743中的更多细节
以上所有答案直接回答了问题。但是,这不是一个直接的解决方案,而是一个可能更重要的想法,可以激发思想。
由于行长是任意的,因此需要读取文件第n行之前的所有字节。如果您有一个巨大的文件,或者需要多次重复执行此任务,并且此过程非常耗时,那么您应该首先认真考虑是否应该以其他方式存储数据。
真正的解决方案是在文件的开头具有一个索引,以指示行开始的位置。您可以使用数据库格式,也可以只在文件的开头添加一个表。或者,创建一个单独的索引文件来伴随您的大文本文件。
例如,您可以为换行符创建一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
Run Code Online (Sandbox Code Playgroud)
然后使用读取tail,实际上seek直接指向文件中的相应点!
例如获得第1000行:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
Run Code Online (Sandbox Code Playgroud)