我只想通过 awk 打印输入中除最后三行之外的所有行。请注意,我的文件包含 n 行。
例如,
file.txt
包含,
foo
bar
foobar
barfoo
last
line
我希望输出是,
foo
bar
foobar
tac
我知道这可以通过和sed
或tac
和 的组合来实现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 具有用于数组的push
和pop
运算符,那么会更容易。
或者我们可以预先计算行数以及我们实际上想要走多远$(($(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 秒(我)awk
3 个变量:0.178 秒(me)awk
双排:0.322 秒(terdon)awk
循环缓冲区:0.355 秒(Scrutinizer)awk
for循环:0.693秒(我)
最快的解决方案是使用 C 优化实用程序,如head
或wc
处理繁重的工作,但在纯的 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
工具(NR
和FNR
)完成的。我使用以下命令对一个 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