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。
pee
は tee
とは違い、標準入力の内容を標準出力に出力しません。パイプで使うことに特化した 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'
-
次のコメントを参照: #comment-38a252900c08a1132973 ↩ ↩2