awk 命令打印除最后三行之外的所有行

awk 命令打印除最后三行之外的所有行

我只想通过 awk 打印输入中除最后三行之外的所有行。请注意,我的文件包含 n 行。

例如,

file.txt包含,

foo
bar
foobar
barfoo
last
line

我希望输出是,

foo
bar
foobar

tac我知道这可以通过和sedtac和 的组合来实现awk

$ tac file | sed '1,3d' | tac
foo
bar
foobar

$ tac file | awk 'NR==1{next}NR==2{next}NR==3{next}1' | tac
foo
bar
foobar

但我只想通过 awk 获取输出。

答案1

它非常笨重,但你可以将每一行添加到一个数组中,最后 — — 当您知道长度时 — — 输出除最后 3 行之外的所有内容。

... | awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}'

另一个(更有效这里) 方法是手动堆叠三个变量:

... | awk '{if (a) print a; a=b; b=c; c=$0}'

a仅在一行从 移动到c然后b移动到 之后才打印a,因此这会将其限制为三行。直接的好处是它不会将所有内容存储在内存中,并且不会造成缓冲问题(fflush()如果打印后出现缓冲问题),但缺点是扩展起来并不容易。如果您想跳过最后 100 行,则需要 100 个变量和 100 个变量组合。

如果 awk 具有用于数组的pushpop运算符,那么会更容易。

或者我们可以预先计算行数以及我们实际上想要走多远$(($(wc -l < file) - 3))。这对于流式传输内容,但在文件上,效果很好:

awk -v n=$(($(wc -l < file) - 3)) 'NR<n' file

一般来说你只需要使用head

$ seq 6 | head -n-3
1
2
3

使用terdon 的基准我们实际上可以看到它们之间的比较。不过我想提供一个完整的比较:

  • head:0.018 秒(我)
  • awk+ wc:0.169 秒(我)
  • awk3 个变量:0.178 秒(me)
  • awk双排:0.322 秒(terdon)
  • awk循环缓冲区:0.355 秒(Scrutinizer)
  • awkfor循环:0.693秒(我)

最快的解决方案是使用 C 优化实用程序,如headwc处理繁重的工作,但在纯的 awk,目前手动旋转堆栈是王道。

答案2

为了尽量减少内存使用,您可以使用循环缓冲区:

awk 'NR>n{print A[NR%n]} {A[NR%n]=$0}' n=3 file

通过对行号使用 mod 运算符,我们最多可以获得 n 个数组条目。

以 n=3 为例:

第 1 行NR%n等于 1,第 2 行产生 2,第 3 行产生 0,第 4 行再次计算为 1。

Line 1 -> A[1]
Line 2 -> A[2]
Line 3 -> A[0]
Line 4 -> A[1]
Line 5 -> A[2]
...

当我们到达第 4 行时,A[NR%n]它包含第 1 行的内容。因此,它会被打印出来并A[NR%n]获取第 4 行的内容。下一行(第 5 行)会打印第 2 行的原始内容,依此类推,直到最后。剩余未打印的是缓冲区的内容,其中包含最后 3 行...

答案3

您还可以对文件进行两次处理,以避免保留任何事物在记忆中:

awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file

这里的技巧是NR==FNR测试。NR是当前行号,FNR是当前文件的当前行号。如果输入了多个文件,则只有在处理第一个文件时FNR才会等于NR。这样,我们就可以快速获取第一个文件中的行数并将其保存为c。由于“两个”文件实际上是同一个文件,我们现在知道我们想要的行数,因此只有当这是其中之一时我们才会打印。

虽然你可能认为这种方法比其他方法慢,但实际上它更快,因为几乎没有任何处理过程。除了一次算术比较之外,一切都是使用内部awk工具(NRFNR)完成的。我使用以下命令对一个 50MB 的文件进行了测试,该文件包含一百万行:

for i in {500000..1000000}; do 
    echo "The quick brown fox jumped over the lazy dog $i" >> file; 
done

如您所见,时间几乎相同,但我在此处提供的方法比 Oli 的第一个建议略快(但比其他方法慢):

$ for i in {1..10}; do ( 
    time awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file > /dev/null ) 2>&1 | 
       grep -oP 'real.*?m\K[\d\.]+'; 
  done | awk '{k+=$1}END{print k/10" seconds"}'; 
0.4757 seconds

$  for i in {1..10}; do ( 
    time awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}' file > /dev/null ) 2>&1 | 
        grep -oP 'real.*?m\K[\d\.]+'; 
   done | awk '{k+=$1}END{print k/10" seconds"}'; 
0.5347 seconds

答案4

我知道问题具体是关于的awk,但为了简洁起见,人们总是可以使用:

head -n -3

相关内容