web-dev-qa-db-ja.com

最後の3行を除くすべての行を印刷するawkコマンド

入力からawkまでの最後の3行のみを除くすべての行を印刷したい。ファイルにはn行の行が含まれていることに注意してください。

たとえば

file.txtを含む、

foo
bar
foobar
barfoo
last
line

出力をしたい、

foo
bar
foobar

tacsedまたはtacawkの組み合わせで可能になると思います

$ 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のみを介して出力が必要です。

5
Avinash Raj

非常に不格好ですが、配列にすべての行を追加し、最後に(長さがわかれば)最後の3行を除くすべてを出力できます。

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

別の(より効率的なhere)アプローチは、3つの変数に手動でスタックします。

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

aは、行がcからbに移動してからaに移動した後にのみ出力されるため、3行に制限されます。差し迫った欠点は、すべてのコンテンツをメモリに保存しないことであり、バッファリングの問題を引き起こすことはありません(印刷する場合はfflush())、しかしここでの欠点はこれをスケールアップするのが簡単ではないことです。最後の100行をスキップするには、100個の変数と100個の変数ジャグリングが必要です。

Awkに配列用のPushおよびpop演算子があれば、簡単になります。

または、行数と$(($(wc -l < file) - 3))を実際に使用する範囲を事前に計算することもできます。これはstreamedコンテンツには比較的役に立たないが、ファイル上ではかなりうまく機能する:

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.169s(me)
  • awk 3変数:0.178s(me)
  • awkダブルファイル:0.322s(terdon)
  • awk循環バッファー:0.355s(Scrutinizer)
  • awk for-loop:0.693s(me)

最速の解決策は、headwcなどのC最適化ユーティリティを使用することです。しかし、pureawkでは、今のところ手動で回転するスタックが重要です。

16
Oli

メモリ使用量を最小限に抑えるには、循環バッファーを使用できます。

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行目)最後まで印刷するなど。まだ印刷されていないのは、最後の3行を含むバッファーの内容です...

5
Scrutinizer

anythingをメモリに保持しないように、ファイルを2回処理することもできます。

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

ここでのトリックは、NR==FNRテストです。 NRは現在の行番号で、FNRは現在のファイルの現在の行番号です。複数のファイルが入力として渡される場合、FNRは、最初のファイルが処理されている間のみNRと等しくなります。この方法で、最初のファイルの行数をすばやく取得し、cとして保存します。 「2つの」ファイルは実際には同じファイルなので、必要な行数がわかったので、これがそのうちの1つである場合にのみ印刷します。

これは他のアプローチよりも遅いと思うかもしれませんが、処理がほとんど行われないため、実際には高速です。すべては、単一の算術比較とは別に、内部awkツール(NRおよびFNR)を使用して行われます。このコマンドで作成された100万行の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
2
terdon

質問はawkに関するものでしたが、簡潔にするために常に使用できます。

head -n -3
0
sjas