これは宿題ですが、具体的な宿題については質問しません。
1つのファイルからさまざまな行のセットを取得するには、ヘッドとテールを使用する必要があります。 6-11行目と19-24行目と同じように、両方を別のファイルに保存します。私は追加などを使用してこれを行うことができることを知っています
head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1.
しかし、私たちは私たちがそうすることになっているとは思いません。
headコマンドとtailコマンドを組み合わせてファイルに保存する特定の方法はありますか?
次のような構成を使用して{ ... ; }
でコマンドをグループ化すると、head
のみと基本的な算術でそれを行うことができます
{ head -n ...; head -n ...; ...; } < input_file > output_file
ここで、すべてのコマンドが同じ入力を共有します( @ mikeserv に感謝します)。
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
{ … }
グループ化構文を使用して、リダイレクト演算子を複合コマンドに適用できます。
{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1
最初のM + N行を複製して最後のN行だけを保持する代わりに、最初のM行をスキップして次のN行を複製できます。これは 大きなファイルではかなり高速 です。 tail
の+N
引数はスキップする行数ではなく、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
繰り返しになりますが、これはバッファリングが原因で機能するとは限りません。 GNU coreutils(非組み込みLinuxシステムで見つかったもの))からのhead
コマンドで、入力が通常のファイルからのものである場合、たまたま機能します。 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
sed
を使用すると、次のことができます。
sed '24q;1,5d;12,18d' <infile >outfile
...head
を使用すると、より効率的なソリューションが得られる可能性があります。ドンはそれがどのようにうまく機能するかをすでに示しましたが、私もそれをいじっています。この特定のケースを処理するためにあなたがするかもしれない何か:
for n in 5 6 7 6
do head -n"$n" >&"$((1+n%2))"
done <infile >outfile 2>/dev/null
...head
を4回呼び出し、outfile
または/dev/null
への書き込みを4回繰り返して、$n
の反復の値が偶数か奇数かによって異なります。
より一般的なケースでは、私はこれをすでに持っている他のいくつかのものからまとめました:
somehead()(
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
set -e -- "${1#-}" "$@" #-e for arg validation
r=; cd -- "${TMP:-/tmp}" #go to tmp
dd bs=4096 of="$$$$" <&4 2>&3 & #dd <in >tmpfile &bg
until [ -s "$$$$" ]; do :; done #wait while tmpfile empty
exec <"$$$$" 4<&-; rm "$$$$" #<tmpfile; rm tmpfile
[ "$3${1}0" -ne "$3${2#?}0" ] || #validate args - chk $1
shift "$(((r=-${1:--1})||1))"; shift #shift 1||2
while [ "$(((r+=(_n=1))-1))" -ne 0 ] && #while ! $rptmax &&
IFS= read -r l && # ! EOF &&
printf "%.$(($1>0?${#l}+1:0))s" "$l # ? printf do
"; do for n do [ "${n#-}" -gt 0 ] || exit #args all -[nums>0]
head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
done; done #done and done
) 4<&0 3>/dev/null #4<for dd 3>for head
これはあなたのようなことができます:
seq 100 | somehead -1 -5 6 -7 6
...印刷する...
6
7
8
9
10
11
19
20
21
22
23
24
最初の引数が-
で始まる繰り返しカウントであるか、失敗した場合は-
のみであると想定しています。カウントが指定されている場合は、後続の引数で指定された行パターンを指定された回数だけ繰り返し、その直後に停止します。
続く引数ごとに、負の整数を解釈して/dev/null
に書き込む必要がある行数を示し、正の整数を解釈してstdout
に書き込む必要がある行数を示します。
したがって、上記の例では、最初の5行を/dev/null
に、次の6行をstdout
に、次の7行を/dev/null
に、次の6行をstdout
。引数の最後に到達し、-1
繰り返し回数を完全に循環した後、終了します。最初の引数が-2
であった場合、プロセスをもう一度繰り返したか、または-
であったかどうかを可能な限り繰り返します。
Argサイクルごとに、while
ループが1回処理されます。各ループの先頭で、stdin
の最初の行がシェル変数$l
に読み込まれます。これは、while head </dev/null; do :; done
が無期限に繰り返されるために必要です。head
は、ファイルの終わりに達したときに戻りを示します。したがって、EOFに対するチェックはread
に専用であり、printf
は$l
とstdout
への改行を書き込みます。 2番目の引数は正の整数です。
read
チェックはループを少し複雑にします。これは、別のループが呼び出された直後に-の繰り返しごとに2-$#
で表されるargs $n
を反復するfor
ループが原因です。その親while
ループ。つまり、反復ごとに、最初の引数はコマンドラインで指定された値から1ずつ減らされる必要がありますが、他のすべての引数は元の値を保持する必要があるため、$_n
マーカー変数の値がそれぞれから減算されます。ただし、最初の引数には0より大きい値しか保持しません。
これは関数のメインループを構成しますが、コードの大部分は先頭にあり、関数がパイプであっても入力としてクリーンにバッファーできるようにすることを目的としています。これは、最初にバックグラウンドのdd
を呼び出して、4k個のブロックサイズで出力のtmpfileにコピーすることで機能します。次に、関数はホールドループを設定します。これは、単一の完全なサイクルであってもほとんど完了しないはずです-関数がそのstdinをファイルに置き換える前に、dd
が少なくとも1つのファイルへの書き込みを確実に行うためです。記述子はtmpfileにリンクされ、その後すぐにrm
でファイルのリンクを解除します。これにより、関数はトラップやクリーンアップを必要とせずにストリームを確実に処理できます。関数がfdで解放するとすぐに、名前付きのファイルシステムリンクのみがすでに削除されているため、tmpfileは存在しなくなります。
頭と尾を使用する必要があるとおっしゃっていましたが、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 -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1
ファイルの行数とブロック数が多いほど、sedはより効率的になります。
次のような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
この場合、これは少しやり過ぎですが、フィルターが大きくなると、恩恵を受けることができます。