这样做是否有“规范”方式?我一直在使用head -n | tail -1
来解决问题,但我一直想知道是否有一个Bash工具专门从文件中提取一行(或一系列行)。
“canonical”是指一个程序,其主要功能是这样做。
答案 0 :(得分:651)
head
和tail
管道对于一个巨大的文件来说会很慢。我建议sed
像这样:
sed 'NUMq;d' file
其中NUM
是您要打印的行号;所以,例如,sed '10q;d' file
打印file
的第10行。
说明:
当行号为NUMq
时, NUM
会立即退出。
d
会删除该行而不是打印它;这在最后一行被禁止,因为q
导致在退出时跳过其余的脚本。
如果变量中有NUM
,则需要使用双引号而不是单引号:
sed "${NUM}q;d" file
答案 1 :(得分:258)
sed -n '2p' < file.txt
将打印第2行
sed -n '2011p' < file.txt
2011th line
sed -n '10,33p' < file.txt
第10行到第33行
sed -n '1p;3p' < file.txt
第1和第3行
依旧......
要添加带有sed的行,您可以查看:
答案 2 :(得分:79)
我有一个独特的情况,我可以对此页面上提出的解决方案进行基准测试,因此我将此答案作为所提议解决方案的合并,并为每个解决方案包含运行时间。
设置
我有一个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
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 强>
我也运行了现有的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
答案 3 :(得分:44)
使用awk
非常快:
awk 'NR == num_line' file
如果是这样,则执行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
答案 4 :(得分:26)
试试这个:
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
或gawk
命令)。
是否有工具只能打印特定的线?不是标准工具之一。但是,sed
可能是最接近和最简单的用途。
答案 5 :(得分:20)
# print line number 52
sed '52!d' file
答案 6 :(得分:20)
这个问题被标记为Bash,这是Bash(≥4)的做法:将mapfile
与-s
(跳过)和-n
(计数)选项一起使用。
如果您需要获取文件的第42行file
:
mapfile -s 41 -n 1 ary < file
此时,您将拥有一个数组ary
,其中的字段包含file
行(包括尾随换行符),我们跳过前41行({{1 }}),并在读取一行(-s 41
)后停止。所以这真的是第42行。打印出来:
-n 1
如果您需要一系列线条,比如42-666(含)范围,并说您不想自己做数学,并在标准输出上打印:
printf '%s' "${ary[0]}"
如果您还需要处理这些行,那么存储尾随换行符并不是很方便。在这种情况下,使用mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"
选项(修剪):
-t
你可以有一个功能为你做这个:
mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"
没有外部命令,只有Bash builtins!
答案 7 :(得分:12)
根据我的测试,在性能和可读性方面,我的建议是:
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'
很有意思,但我认为开箱即用的人比头/尾解决方案更少,并且它也比尾/头慢。< / p>
在我的测试中,尾部/头部版本的表现始终优于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
答案 8 :(得分:11)
您也可以使用sed打印并退出:
sed -n '10{p;q;}' file # print line 10
答案 9 :(得分:7)
您也可以使用Perl:
perl -wnl -e '$.== NUM && print && exit;' some.file
答案 10 :(得分:6)
对于大文件,最快的解决方案始终是尾部,只要两个距离:
$.cookie('menu-data', "Hello");
alert($.cookie("menu-data"));
S
是众所周知的。然后,我们可以使用它:
E
howmany只是所需行数。
中的更多细节答案 11 :(得分:4)
作为CaffeineConnoisseur的一个非常有用的基准测试答案的后续内容......我很好奇地看到了#map;方法与其他方法进行了比较(因为没有经过测试),所以我尝试了一个快速和肮脏的速度比较,因为我有bash 4方便。投入了对尾巴的测试头&#34;方法(而不是头部)在我对它的最佳答案的评论之一中提到,因为人们正在赞美它。我没有任何与所用测试文件大小差不多的东西;我能在短时间内找到的最好的是一个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
希望这有帮助!
答案 12 :(得分:4)
以上所有答案都直接回答了这个问题。但这是一个不太直接的解决方案,但却是一个可能更重要的想法,以引发思考。
由于行长度是任意的,因此需要读取第n行之前的文件的所有字节。如果您有一个庞大的文件或需要多次重复此任务,并且此过程非常耗时,那么您应该认真考虑是否应该首先以不同的方式存储数据。
真正的解决方案是拥有一个索引,例如在文件的开头,指示行开始的位置。您可以使用数据库格式,也可以只在文件的开头添加一个表。或者,在大文本文件旁边创建一个单独的索引文件。
e.g。您可以为换行创建一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
然后使用tail
阅读,其中seek
直接到文件中的相应位置!
e.g。得到第1000行:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
答案 13 :(得分:3)
如果你有多个由\ n分隔的行(通常是新行)。你也可以使用'cut':
echo "$data" | cut -f2 -d$'\n'
您将从文件中获取第二行。 -f3
为您提供第3行。
答案 14 :(得分:2)
已经有很多好的答案。我个人跟awk一起去。为方便起见,如果您使用bash,只需将以下内容添加到~/.bash_profile
即可。并且,下次登录时(或者如果您在此更新后获取.bash_profile),您将有一个新的漂亮的“第n”函数可用于管理文件。
执行此操作或将其放入〜/ .bash_profile(如果使用bash)并重新打开bash(或执行source ~/.bach_profile
)
# print just the nth piped in line
nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
然后,要使用它,只需通过它。 。E.g,:
$ yes line | cat -n | nth 5
5 line
答案 15 :(得分:1)
使用带有变量作为行号的sed打印第n行:
a=4
sed -e $a'q:d' file
这里'-e'标志用于将脚本添加到要执行的命令。
答案 16 :(得分:1)
使用别人提到的内容,我希望这是一个快速的&amp;我的bash shell中的花花公子功能。
创建文件:~/.functions
添加内容:
getline() {
line=$1
sed $line'q;d' $2
}
然后将其添加到您的~/.bash_profile
:
source ~/.functions
现在,当您打开一个新的bash窗口时,您可以按原样调用该函数:
getline 441 myfile.txt
答案 17 :(得分:1)
在查看了the top answer和the benchmark之后,我实现了一个很小的帮助函数:
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
答案 18 :(得分:0)
我已经将上面的一些答案放入了一个简短的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
答案 19 :(得分:0)
保存两次击键,不使用括号打印第N行:
sed -n Np <fileName>
^ ^
\ \___ 'p' for printing
\______ '-n' for not printing by default
例如打印第 100 行:
sed -n 100p foo.txt