head と tail を使用して異なる行セットを取得し、同じファイルに保存する

head と tail を使用して異なる行セットを取得し、同じファイルに保存する

これは宿題ですが、具体的な宿題の質問はしません。

1つのファイルから異なる行セットを取得するには、headとtailを使用する必要があります。つまり、6行目から11行目と19行目から24行目を取り出し、両方を別のファイルに保存します。appendを使用してこれを行うことができることはわかっています。

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

しかし、そうする必要はないと思います。head
コマンドと tail コマンドを組み合わせてファイルに保存する具体的な方法はありますか?

答え1

次のような構文を使ってheadコマンドをグループ化すれば、単独でも基本的な算術演算でも実行できます。{ ... ; }

{ head -n ...; head -n ...; ...; } < input_file > output_file

すべてのコマンドは同じ入力を共有します(マイクサーブ
6 行目から 11 行目と 19 行目から 24 行目を取得することは、次のコードと同等です 。

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

つまり、基本的には次のように実行します。

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

答え2

グループ化構造を使用して{ … }、複合コマンドにリダイレクト演算子を適用できます。

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

最初のM+N行を複製して最後のN行だけを残す代わりに、最初のM行をスキップして次のN行を複製することができます。これは大きなファイルでは測定可能なほど高速化+Nの引数はtailスキップする行数ではなく、それに 1 を加えた数、つまり 1 から番号が付けられた行で印刷する最初の行の番号であることに注意してください。

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

どちらの方法でも、出力ファイルは 1 回だけ開かれますが、入力ファイルは抽出するスニペットごとに 1 回走査されます。入力をグループ化するのはどうでしょうか?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

一般的には、これは機能しません。(少なくとも入力が通常のファイルである場合は、一部のシステムでは機能するかもしれません。)なぜでしょうか?入力バッファリングを含むほとんどのプログラムは、tail入力をバイトごとに読み取るのではなく、数キロバイトずつ読み取ります。これは、その方が高速だからです。したがって、tailは数キロバイトを読み取り、先頭で少しスキップし、さらに少しを に渡してhead停止します。ただし、読み取られたものは読み取られただけであり、次のコマンドでは使用できません。

別のアプローチheadパイプを使用することです/dev/null行をスキップします。

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

これもバッファリングのため、動作が保証されていません。head入力が通常のファイルである場合、GNU coreutils(非組み込みLinuxシステムにあるもの)のコマンドで動作します。これは、この実装がhead必要なものを読み込んだ後、ファイルの位置を設定する出力されなかった最初のバイトまで。入力がパイプの場合は機能しません。

ファイルから複数の行のシーケンスを印刷するより簡単な方法は、次のようなより汎用的なツールを呼び出すことです。sedまたはawk(遅くなる可能性がありますが、これは非常に大きなファイルの場合にのみ問題になります。)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

答え3

head と tail を使用する必要があるとおっしゃっていますが、この作業には sed の方が間違いなく簡単なツールです。

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

他のプロセスを使用して文字列内のブロックを構築し、それを sed で実行することもできます。

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n は出力を否定し、次に p で印刷する範囲を指定します。範囲の最初と最後の数字はコンマで区切られます。

そうは言っても、@don_crissti が提案したコマンドのグループ化を実行するか、ファイルを数回ループして、そのたびに head/tail で行のチャンクを取得することもできます。

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

ファイル内の行数とブロック数が増えるほど、sed の効率が上がります。

答え4

次のような bash 関数を使用します。

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

この場合、これは少しやり過ぎですが、フィルターが大きくなればメリットになる可能性があります。

関連情報