Answers:
head
和管道tail
对于大型文件来说会很慢。我建议sed
这样:
sed 'NUMq;d' file
NUM
您要打印的行号在哪里;因此,例如,sed '10q;d' file
打印的第十行file
。
说明:
NUMq
行号为时将立即退出NUM
。
d
将删除该行而不是打印它;在最后一行禁止这样做,因为q
退出时会导致脚本的其余部分被跳过。
如果您有NUM
一个变量,则需要使用双引号而不是单引号:
sed "${NUM}q;d" file
sed -n 'NUMp'
和sed 'NUM!d'
解决方案快6到9倍。
tail -n+NUM file | head -n1
可能会一样快或更快。至少,当我尝试使用NUM是250000且文件行数为100万行时,它在系统上的速度(明显)更快。YMMV,但我真的不明白为什么会这样。
cat
确实确实更快(几乎快一倍),但前提是尚未缓存文件。缓存文件后,直接使用filename参数的速度更快(大约快1/3),而cat
性能保持不变。奇怪的是,在OS X 10.9.3上,这似乎没有任何区别:cat
/ no cat
,是否缓存文件。@anubhava:我很高兴。
sed 'NUMq
将输出第一个NUM
文件,;d
并删除除最后一行以外的所有文件。
sed -n '2p' < file.txt
将打印第二行
sed -n '2011p' < file.txt
2011年
sed -n '10,33p' < file.txt
第10行到第33行
sed -n '1p;3p' < file.txt
第一和第三行
等等...
要使用sed添加行,您可以检查以下内容:
<
在这种情况下是不必要的。简而言之,我偏爱使用重定向,因为我经常使用重定向,例如sed -n '100p' < <(some_command)
-通用语法:)。它并不是很有效,因为重定向是在派生自身时使用shell完成的,所以...它只是一个首选项...(是的,它是一个字符长):)
head
/ tail
不能解决问题sed -n '1p;3p'
-也可以打印更多不相邻的行...
我有一个独特的情况,可以在此页面上对提出的解决方案进行基准测试,因此我将这个答案写成对提出的解决方案的合并,其中包括每个解决方案的运行时间。
设定
我有一个3.261 GB的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
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万行,但是无论如何这似乎都不是解决该问题的可行方法。
AWK
我只使用解决方案,exit
因为我不想等待完整文件运行:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
这段代码在00:01:16.583中运行,仅慢了约1秒,但仍然没有改善基线。以这种速度,如果排除了退出命令,则可能要花费大约76分钟才能读取整个文件!
佩尔
我也运行了现有的Perl解决方案:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
这段代码在00:01:13.146中运行,比基线快2秒钟。如果我以全部500,000,000的价格运行它,则可能需要约12分钟。
sed
董事会最重要的答案是我的结果:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
这段代码在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行
sed
perl
head|tail
awk
cut
第500,000,000行
sed
perl
head|tail
awk
cut
第3,338,559,320行
sed
perl
head|tail
awk
cut
有了awk
它很快:
awk 'NR == num_line' file
如果为true,awk
则执行的默认行为:{print $0}
。
如果您的文件碰巧很大,则最好exit
阅读必填的行。这样可以节省CPU时间。请参见答案末尾的时间比较。
awk 'NR == num_line {print; exit}' file
如果要从bash变量中提供行号,可以使用:
awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file # equivalent
查看使用可以节省多少时间exit
,特别是如果该行恰好位于文件的第一部分中时:
# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines
$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla
real 0m1.303s
user 0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla
real 0m0.198s
user 0m0.178s
sys 0m0.013s
因此相差0.198s和1.303s,快了6倍。
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
。使用GNU awk可以使用加快速度awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
。
FS=RS
避免字段分裂?
FS=RS
不会避免字段拆分,但只会解析$ 0,并且仅分配一个字段,因为没有RS
in$0
FS=RS
但没有发现时间上的差异。那我问一个问题以便您可以扩展呢?谢谢!
根据我的测试,就性能和可读性而言,我的建议是:
tail -n+N | head -1
N
是您想要的行号。例如,tail -n+7 input.txt | head -1
将打印文件的第7行。
tail -n+N
将打印从line开始的所有内容N
,并head -1
使其在一行之后停止。
替代方案head -N | tail -1
可能更具可读性。例如,这将打印第七行:
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
这是我的机器上运行的输出(带有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
head | tail
vs 之间的性能是否不同tail | head
?还是取决于打印的是哪行(文件开头还是文件结尾)?
head -5 | tail -1
VS tail -n+5 | head -1
。实际上,我找到了另一个答案进行了测试比较,发现tail | head
更快。stackoverflow.com/a/48189289
哇,所有的可能性!
尝试这个:
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
(您可能必须尝试使用nawk
or gawk
命令)。
有没有只打印特定行的工具?不是标准工具之一。但是,sed
可能是最接近和最简单的使用方法。
# print line number 52
sed '52!d' file
这个问题被标记为Bash,这是Bash(≥4)的处理方式:mapfile
与-s
(skip)和-n
(count)选项一起使用。
如果您需要获取文件的第42行file
:
mapfile -s 41 -n 1 ary < file
至此,您将获得一个数组ary
,该数组的字段包含的行file
(包括尾随的换行符),我们已跳过了前41行(-s 41
),并在读取了一行(-n 1
)之后停止了。这就是第42行。要打印出来:
printf '%s' "${ary[0]}"
如果您需要一定范围的行,请说范围为42–666(含),并说您不想自己做数学,并在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[@]}"
您可以使用一个函数为您执行此操作:
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内置函数!
对于大文件,最快的解决方案始终是tail | head,前提是两个距离:
S
E
众所周知。然后,我们可以使用以下代码:
mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"
多少只是所需的行数。
S
和E
,(即字节,字符,或线)。
以上所有答案直接回答了问题。但是,这不是一个直接的解决方案,而是一个可能更重要的想法,可以激发思想。
由于行长是任意的,因此需要读取文件第n行之前的所有字节。如果您有一个巨大的文件,或者需要多次重复执行此任务,并且此过程很耗时,那么您应该首先认真考虑是否应该以其他方式存储数据。
真正的解决方案是在文件的开头有一个索引,指示行开始的位置。您可以使用数据库格式,也可以只在文件的开头添加一个表。或者,创建一个单独的索引文件来伴随您的大文本文件。
例如,您可以为换行符创建一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
然后使用读取tail
,实际上seek
直接指向文件中的相应点!
例如获得第1000行:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
作为CaffeineConnoisseur很有帮助的基准测试答案的后续措施……我很好奇“ mapfile”方法与其他方法相比有多快(因为未经测试),因此我自己尝试了快速和较慢的速度比较我确实有方便的bash 4。当我在上面回答时,对其中一个注释中提到的“尾|头”方法(而不是头|尾)进行了测试,因为人们正在赞美它。我没有所用测试文件大小的任何东西;在短时间内,我能找到的最好的文件是一个14M的谱系文件(用空格分隔的长行,不到12000行)。
简短版本:mapfile的显示速度比cut方法快,但比其他所有方法都慢,所以我称其为dud。尾巴 头,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
希望这可以帮助!
使用其他人提到的内容,我希望它成为bash shell中的快速功能。
创建一个文件: ~/.functions
添加内容:
getline() {
line=$1
sed $line'q;d' $2
}
然后将其添加到您的~/.bash_profile
:
source ~/.functions
现在,当您打开一个新的bash窗口时,您可以这样调用该函数:
getline 441 myfile.txt
如果您用\ n分隔多行(通常是新行)。您也可以使用“剪切”:
echo "$data" | cut -f2 -d$'\n'
您将从文件中获得第二行。-f3
给你第三行。
cat FILE | cut -f2,5 -d$'\n'
将显示FILE的第2行和第5行。(但它不会保留顺序。)
已经有很多好的答案。我个人与awk一起去。为了方便起见,如果您使用bash,请将以下内容添加到中~/.bash_profile
。而且,下次登录时(或者如果您在此更新后获取.bash_profile的资源),您将可以使用新的漂亮的“ nth”函数来传送文件。
执行此操作或将其放入〜/ .bash_profile(如果使用bash),然后重新打开bash(或执行source ~/.bach_profile
)
# print just the nth piped in line
nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
然后,要使用它,只需通过它进行管道传输即可。例如,:
$ yes line | cat -n | nth 5
5 line
我已经将上面的一些答案放入了一个简短的bash脚本中,您可以将其放入一个名为get.sh
并链接到的文件/usr/local/bin/get
(或您喜欢的任何其他名称)中。
#!/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
确保其可执行
$ chmod +x get
链接它,使之可在PATH
与
$ ln -s get.sh /usr/local/bin/get
负责任地享受!
P
awk
和sed
,我相信有人也可以提出一种Perl方案;)