如何从文件中读取前n行和后n行?

时间:2015-02-19 20:10:03

标签: bash awk sed head tail

我想知道如何读取文件的前n行和最后n行?对于n=2,我读了(head -n2 && tail -n2) $ cat x 1 2 3 4 5 $ cat x | (head -n2 && tail -n2) 1 2 可以使用,但它没有。

n=2

1 2 4 5 的预期输出为:

{{1}}

9 个答案:

答案 0 :(得分:6)

head -n2 file && tail -n2 file

答案 1 :(得分:3)

你可能会想要这样的东西:

... | awk -v OFS='\n' '{a[NR]=$0} END{print a[1], a[2], a[NR-1], a[NR]}'

或者如果你需要指定一个数字并考虑到@ Wintermute的精明观察,你不需要缓冲整个文件,这就是你真正想要的东西:

... | awk -v n=2 'NR<=n{print;next} {buf[((NR-1)%n)+1]=$0}
         END{for (i=1;i<=n;i++) print buf[((NR+i-1)%n)+1]}'

我认为数学是正确的 - 希望你能想到使用由NR调整的缓冲区大小的旋转缓冲区并调整为使用范围1-n而不是0-(n)的索引-1)。

为了帮助理解上面索引中使用的模数运算符,下面是一个中间打印语句的示例,用于在执行时显示逻辑:

$ cat file   
1
2
3
4
5
6
7
8

$ cat tst.awk                
BEGIN {
    print "Populating array by index ((NR-1)%n)+1:"
}
{
    buf[((NR-1)%n)+1] = $0

    printf "NR=%d, n=%d: ((NR-1 = %d) %%n = %d) +1 = %d -> buf[%d] = %s\n",
        NR, n, NR-1, (NR-1)%n, ((NR-1)%n)+1, ((NR-1)%n)+1, buf[((NR-1)%n)+1]

}
END { 
    print "\nAccessing array by index ((NR+i-1)%n)+1:"
    for (i=1;i<=n;i++) {
        printf "NR=%d, i=%d, n=%d: (((NR+i = %d) - 1 = %d) %%n = %d) +1 = %d -> buf[%d] = %s\n",
            NR, i, n, NR+i, NR+i-1, (NR+i-1)%n, ((NR+i-1)%n)+1, ((NR+i-1)%n)+1, buf[((NR+i-1)%n)+1]
    }
}
$ 
$ awk -v n=3 -f tst.awk file
Populating array by index ((NR-1)%n)+1:
NR=1, n=3: ((NR-1 = 0) %n = 0) +1 = 1 -> buf[1] = 1
NR=2, n=3: ((NR-1 = 1) %n = 1) +1 = 2 -> buf[2] = 2
NR=3, n=3: ((NR-1 = 2) %n = 2) +1 = 3 -> buf[3] = 3
NR=4, n=3: ((NR-1 = 3) %n = 0) +1 = 1 -> buf[1] = 4
NR=5, n=3: ((NR-1 = 4) %n = 1) +1 = 2 -> buf[2] = 5
NR=6, n=3: ((NR-1 = 5) %n = 2) +1 = 3 -> buf[3] = 6
NR=7, n=3: ((NR-1 = 6) %n = 0) +1 = 1 -> buf[1] = 7
NR=8, n=3: ((NR-1 = 7) %n = 1) +1 = 2 -> buf[2] = 8

Accessing array by index ((NR+i-1)%n)+1:
NR=8, i=1, n=3: (((NR+i = 9) - 1 = 8) %n = 2) +1 = 3 -> buf[3] = 6
NR=8, i=2, n=3: (((NR+i = 10) - 1 = 9) %n = 0) +1 = 1 -> buf[1] = 7
NR=8, i=3, n=3: (((NR+i = 11) - 1 = 10) %n = 1) +1 = 2 -> buf[2] = 8

答案 2 :(得分:2)

这可能适合你(GNU sed):

sed -n ':a;N;s/[^\n]*/&/2;Ta;2p;$p;D' file

这样可以保持2的窗口(将n替换为n),然后打印前2行,并在文件末尾打印窗口,即最后2行。

答案 3 :(得分:1)

awk -v n=4 'NR<=n; {b = b "\n" $0} NR>=n {sub(/[^\n]*\n/,"",b)} END {print b}'

前n行由NR<=n;覆盖。对于最后n行,我们只跟踪保持最新 n行的缓冲区,重复添加一行到前端并从前面删除一行(在前n行之后)。

使用一系列线而不是一个缓冲区可以更高效地完成它,但即使有数十亿字节的输入,你也可能在大脑时间浪费更多时间来写出来而不是你#&# 39; d通过运行来节省计算机时间。

ETA :由于上述时间估算引起了(现已删除)评论中的一些讨论,我将尝试添加其他信息。

对于一个巨大的文件(100M行,3.9GiB,n = 5),与@ EdMorton的内存缓冲解决方案(仅在30秒内执行)相比,它需要454秒。通过更适度的输入(&#34;仅仅&#34;数百万行),比率相似:4.7s与0.53s。

此解决方案中的所有额外时间似乎都花在了sub()函数中;一小部分也来自字符串连接,而不仅仅是替换数组成员。

答案 4 :(得分:0)

这是一个GNU sed单行,打印前10行和后10行:

gsed -ne'1,10{p;b};:a;$p;N;21,$D;ba'

如果要在它们之间打印' - '分隔符:

gsed -ne'1,9{p;b};10{x;s/$/--/;x;G;p;b};:a;$p;N;21,$D;ba'

如果您使用的是Mac并且没有GNU sed,那么就无法压缩:

sed -ne'1,9{' -e'p;b' -e'}' -e'10{' -e'x;s/$/--/;x;G;p;b' -e'}' -e':a' -e'$p;N;21,$D;ba'

解释

gsed -ne'调用没有自动打印模式空间的sed

-e'1,9{p;b}'打印前9行

-e'10{x;s/$/--/;x;G;p;b}'使用附加的“ - ”分隔符打印第10行

-e':a;$p;N;21,$D;ba'打印最后10行

答案 5 :(得分:0)

如何使用GNU并行?打印前3行和后3行 -

parallel {} -n 3 file ::: head tail

答案 6 :(得分:0)

根据@ dcaswell的回答here,以下sed脚本打印文件的第一行和最后10行:

# make a test file first
testit=$(mktemp -u)
seq 1 100 > $testit
# this sed script:
sed -n ':a;1,10h;N;${x;p;i\
-----
;x;p};11,$D;ba' $testit
rm $testit

产生这个:

1
2
3
4
5
6
7
8
9
10
-----
90
91
92
93
94
95
96
97
98
99
100

答案 7 :(得分:0)

如果使用的外壳程序支持进程替换,则另一种实现方法是写入多个进程,其中一个用于head,另一个用于tail。假设在此示例中,您的输入来自输送长度未知的内容的管道。您只想使用前5行和后10行并将它们传递到另一个管道:

cat | { tee >(head -5) >(tail -10) 1>/dev/null} | cat

使用{}会从组内部收集输出(在进程shell内将有两个不同的程序写入stdout)。 1>/dev/null是为了摆脱多余的副本,tee会尝试写入其自己的标准输出。

这说明了概念和所有活动部分,但实际上可以通过使用tee的STDOUT流而不是将其丢弃来进行一些简化。请注意,这里仍然需要命令分组,以通过下一个管道传递输出!

cat | { tee >(head -5) | tail -15 } | cat

很明显,用实际执行的操作替换管道中的cat。如果您的输入可以处理相同的内容以写入多个文件,则可以完全避免使用tee并避免使用STDOUT。假设您有一个命令可以接受多个-o输出文件名标记:

{ mycommand -o >(head -5) -o >(tail -10)} | cat

答案 8 :(得分:0)

这是另一个awk脚本。 假设头和尾可能重叠。

script.awk

BEGIN {range = 3} # define the head and tail range
NR <= range {print} # output the head; for the first lines in range
{ arr[NR % range] = $0} # store the current line in a rotating array
END { # last line reached
    for (row = NR - range + 1; row <= NR; row++) { # reread the last range lines from array
        print arr[row % range];
    }
}

运行脚本

seq 1 7 | awk -f script.awk

输出

1
2
3
5
6
7

对于头和尾巴重叠:

seq 1 5 |awk -f script.awk


1
2
3
3
4
5