LoginSignup
2

More than 1 year has passed since last update.

posted at

updated at

ファイルの先頭 N 行と末尾 M 行を取り出すワンライナー

tee を使う方法

$ seq 100000 | nl -s: | tee -p >(head -n 10) >(tail -n 3) > /dev/null | sort -un | cut -d: -f2-
1
2
3
4
5
6
7
8
9
10
99998
99999
100000

処理の流れ

最初に nl -s: で行番号を付けます。次に tee -p >(head -n 10) >(tail -n 3) > /dev/null で先頭の 10 行と末尾の 3 行を取り出します。

取り出そうとする行数が入力テキストの行数より大きい場合は重複行が発生します。また、結果行の順序は保証されないため、sort -nu でソートし、重複行を削除します。

ここまでの処理で、以下の状態となります。

$ seq 100000 | nl -s: | tee -p >(head -n 10) >(tail -n 3) > /dev/null | sort -un
     1:1
     2:2
     3:3
     4:4
     5:5
     6:6
     7:7
     8:8
     9:9
    10:10
 99998:99998
 99999:99999
100000:100000

最後に cut -d: -f2- で行番号を削除することで、期待する結果を得ることができます。

上手くいかなかった方法

最初、以下のように書いたところ、期待する結果は得られず、しかも実行するたびに異なる出力が行われてしまいました。

冒頭に出したコマンドとの違いは、tee-p オプションを指定しているかどうかだけです。これについて、コメント欄で @angel_p_57 さんに重要なポイントを教えていただきました。(次の項目へ)

$ seq 100000 | nl -s: | tee >(head -n 10) >(tail -n 3) > /dev/null | sort -un | cut -d: -f2-
1
2
3
4
5
6
7
8
9
10
5893
5894
5895

プロセス置換と tee の -p オプション

tee >(head -n 10) >(tail -n 3) のように >(COMMAND) と書くと、bash のプロセス置換 (process substitution) になります。

プロセス置換は元のコマンドパイプラインとは非同期で実行されます。また、tee はパイプへの書き込みエラーが発生した場合、デフォルトでは直ちに終了します。tee-p--output-error=MODE オプションを指定することで、この動作を変更できます1

今回は -p (--output-error=warn-nopipe と同じ) を指定することで、発生した問題を解決できました。

pee を使う方法

$ seq 100000 | nl -s: | pee 'head -n 10' 'tail -n 3' | sort -nu | cut -d: -f2-
1
2
3
4
5
6
7
8
9
10
99998
99999
100000

moreutils に含まれる pee を利用する方法です。コメント欄で @yoichi22 さんに教えていただきました1

peetee とは違い、標準入力の内容を標準出力に出力しません。パイプで使うことに特化した tee という感じのコマンドです。

処理の流れ

基本的な考え方は tee を使う方法と同じです。

没案

試行錯誤して作ったものの、フィードバックを受けて、より良い方法が分かった今となっては微妙な方法です。

$ seq 100000 | nl -s: | tee >(sed '11,$d') >(tac | sed '4,$d' | tac) > /dev/null | sort -nu | cut -d: -f2-
1
2
3
4
5
6
7
8
9
10
99998
99999
100000

別解 1

入力データがファイルの場合は、次のように書くこともできます。

$ cat a.txt | head -n 10 && cat a.txt | tail -n 3
1
2
3
4
5
6
7
8
9
10
99998
99999
100000

別解 2

コメントで @angel_p_57 さんに教えていただいた sed で実現する方法です。sedでのtailのシミュレート でコードを分解して、一つ一つ解説されています。

seq 100000 | sed -n -e '1p;:L' -e '${p;q};h;n;2,10p;H;g;4,$D;bL'

  1. 次のコメントを参照: #comment-38a252900c08a1132973 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2