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

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


当前回答

保存两次按键,不使用括号打印第N行:

sed  -n  Np  <fileName>
      ^   ^
       \   \___ 'p' for printing
        \______ '-n' for not printing by default 

例如,要打印第100行:

sed -n 100p foo.txt      

其他回答

哇,所有的可能性!

试试看:

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可能是最接近和最简单的用法。

这个问题被标记为Bash,下面是Bash(≥4)的方法:使用带有-s(跳过)和-n(计数)选项的mapfile。

如果需要获取文件文件的第42行:

mapfile -s 41 -n 1 ary < file

此时,您将得到一个数组ary,其中的字段包含文件行(包括尾部换行符),我们跳过了前41行(-s 41),并在读取一行(-n 1)后停止。这真的是第42行。要打印出来:

printf '%s' "${ary[0]}"

如果您需要一系列行,请说出范围42–666(含),并说您不想自己计算,然后将它们打印在标准输出上:

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内置命令!

以上所有答案都直接回答了这个问题。但这是一个不那么直接的解决方案,但可能是一个更重要的想法,可以引起人们的思考。

由于行长度是任意的,因此需要读取文件第n行之前的所有字节。如果您有一个巨大的文件或需要多次重复此任务,并且此过程非常耗时,那么您应该认真考虑是否应该首先以不同的方式存储数据。

真正的解决方案是有一个索引,例如在文件的开头,指示行开始的位置。您可以使用数据库格式,或者在文件开头添加一个表。或者,创建一个单独的索引文件,与大型文本文件一起使用。

例如,您可以为换行符创建一个字符位置列表:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

然后用tail读取,它实际上直接查找文件中的适当点!

例如获得线1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1

这可能不适用于2字节/多字节字符,因为awk是“字符识别”的,但tail不是。我还没有对一个大文件进行测试。另请参阅此答案。或者,将文件拆分为更小的文件!

对于一个巨大的文件来说,头部和尾部的管道会很慢。我建议您这样做:

sed 'NUMq;d' file

其中NUM是要打印的行数;例如,sed’10q;d'文件以打印文件的第10行。

说明:

当行号为NUM时,NUMq将立即退出。

d将删除该行而不是打印该行;这在最后一行被禁止,因为q会导致退出时跳过脚本的其余部分。

如果变量中有NUM,则需要使用双引号而不是单引号:

sed "${NUM}q;d" file

有了awk,速度相当快:

awk 'NR == num_line' file

如果为true,则执行awk的默认行为:{print$0}。


替代版本

如果您的文件恰好很大,最好在读取所需的行后退出。这样可以节省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.198秒对1.303秒,大约快了6倍。