一句话:打印除了最后三行以外的所有行?

8

我想模拟GNU的head -n -3命令,它可以打印除了最后三行以外的所有行。因为FreeBSD上的head命令没有这个功能。所以我考虑使用以下命令:

seq 1 10 | perl -ne ...

这里我使用了10行,但可以是任何大于3的数字。

在FreeBSD的BASH中,是否可以使用Perl或其他方式实现?

一个超级原始的解决方案是:

seq 1 10 | sed '$d' | sed '$d' | sed '$d'
10个回答

11
seq 1 10 | perl -e '@x=("")x3;while(<>){print shift @x;push @x,$_}'
或者
perl -e '@x=("")x3;while(<>){print shift @x;push @x,$_}' file
或者
command | perl -pe 'BEGIN{@x=("")x3}push @x,$_;$_=shift @x'
perl -pe 'BEGIN{@x=("")x3}push @x,$_;$_=shift @x' file

6
与toolic的解决方案不同,这个解决方案不会将整个文件都存储在内存中,只会存储最后读取的3行。 - cjm
也许 -neBEGIN{} - mpapec

9
seq 1 10 | perl -ne 'push @l, $_; print shift @l if @l > 3'

1
迄今为止最佳答案,因为它是唯一一个在开始打印输出之前不会吞下整个文件的。 - LeoNerd
@LeoNerd 这不是唯一的一个。 - mpapec

7

纯bash和简单工具(wc和cut):

head -n $(($(wc -l file | cut -c-8)-3)) file

免责声明 - 我现在没有访问FreeBSD的权限,但这个方法可以在OSX bash上运行。

在FreeBSD的zsh中也可以工作。 - Slaven Rezic
3
这需要读取文件两次,因此无法在管道上使用。 - cjm
@cjm - 确实。我的另一个答案从管道接受输入,并且不会在开头缓冲整个文件:https://dev59.com/8XfZa4cB1Zd3GeqPNBXy#19162619 - Digital Trauma

6

这个可以通过管道或者输入文件来实现:

seq 1 10 | perl -e'@x=<>;print@x[0..$#x-3]'

2
这是一个针对大文件的非理想解决方案,因为它在打印任何内容之前将整个内容都读入到 @x 中,必须将整个内容保存在内存中。如果要寻找一个更简洁的解决方案,可以参考 @greg-bacon 的回复。 - LeoNerd

5

似乎没有人使用 sedtac,那么这里就介绍一下:

$ seq 10 | tac | sed '1,3d' | tac
1
2
3
4
5
6
7

2
如何呢?
 seq 1 10 | perl -ne 'print if ( !eof  )' | perl -ne 'print if ( !eof  )' | perl -ne 'print if ( !eof  )' 

可能更短的是 eofprint - mpapec
@mpapec ,使用 wc -l 命令可能是最快的方法,我只是在开心玩耍。 - michael501
@mpapec,类似于:类似于导出cnt = $(wc -l afile),然后perl脚本:打印文件直到$ENV {cnt} - 3。 - michael501

2
这个awk单行命令似乎可以完成任务:
awk '{a[NR%4]=$0}NR>3{print a[(NR-3)%4]}' file

1

这是一个晚回答,因为我昨天遇到了类似的问题。

这个解决方案具有以下特点:

  • 纯bash
  • 一行代码
  • 仅读取一次输入流
  • 逐行读取输入流,而不是一次性读取

已在Ubuntu、Redhat和OSX上测试通过。

$ seq 1 10 | { n=3; i=1; while IFS= read -r ln; do [ $i -gt $n ] && cat <<< "${buf[$((i%n))]}"; buf[$((i%n))]="$ln"; ((i++)); done; }
1
2
3
4
5
6
7
$ 

它通过将行读入一个n元素数组实现的循环缓冲区来工作。
n是要截取文件末尾的行数。
对于每一行i,我们可以回显循环缓冲区中的i-n行,然后将行i存储在循环缓冲区中。在读取第n行之前,不会有任何回显。(i mod n)是实现循环缓冲区的数组的索引。
因为要求是一行代码,我试图让它相当简洁,但可读性却受到了损失。

1
如果您使用的是4.0或更高版本的bash,则可以仅使用bash完成此操作:
seq 1 10 | (readarray -t LINES; printf '%s\n' "${LINES[@]:(-3)}")

更新:这个会删除最后三行而不是仅显示它们。
seq 1 10 | (readarray -t L; C=${#L[@]}; printf '%s\n' "${L[@]:0:(C > 3 ? C - 3 : 0)}")

为方便起见,它可以放在一个函数中:

function exclude_last_three {
    local L C
    readarray -t L; C=${#L[@]}
    printf '%s\n' "${L[@]:0:(C > 3 ? C - 3 : 0)}"
}

seq 1 10  | exclude_last_three
seq 11 20 | exclude_last_three

它保留最后3行而不是删除它们。 - Sandra Schlichting

0

另一种 Awk 解决方案,仅使用最少量的缓冲区,并快速打印行,无需先读取所有行。它还可以与管道和大文件一起使用。

awk 'BEGIN{X = 3; for(i = 0; i < X; ++i)getline a[i]}{i %= X; print a[i]; a[i++] = $0}'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接