web-dev-qa-db-ja.com

ヘッドとテールを使用して異なる行のセットを取得し、同じファイルに保存する

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

1つのファイルからさまざまな行のセットを取得するには、ヘッドとテールを使用する必要があります。 6-11行目と19-24行目と同じように、両方を別のファイルに保存します。私は追加などを使用してこれを行うことができることを知っています

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

しかし、私たちは私たちがそうすることになっているとは思いません。
headコマンドとtailコマンドを組み合わせてファイルに保存する特定の方法はありますか?

10
user2709291

次のような構成を使用して{ ... ; }でコマンドをグループ化すると、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
11
don_crissti

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

{ 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の実装は必要なものを読み取り、それが出力しなかった最初のバイトに ファイルの位置を設定 これは、入力がパイプの場合は機能しません。

ファイルから複数の行シーケンスを出力する最も簡単な方法は、 sedawk などのより一般的なツールを呼び出すことです。 (これは遅くなる可能性がありますが、非常に大きなファイルの場合にのみ問題になります。)

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$lstdoutへの改行を書き込みます。 2番目の引数は正の整数です。

readチェックはループを少し複雑にします。これは、別のループが呼び出された直後に-の繰り返しごとに2-$#で表されるargs $nを反復するforループが原因です。その親whileループ。つまり、反復ごとに、最初の引数はコマンドラインで指定された値から1ずつ減らされる必要がありますが、他のすべての引数は元の値を保持する必要があるため、$_nマーカー変数の値がそれぞれから減算されます。ただし、最初の引数には0より大きい値しか保持しません。

これは関数のメインループを構成しますが、コードの大部分は先頭にあり、関数がパイプであっても入力としてクリーンにバッファーできるようにすることを目的としています。これは、最初にバックグラウンドのddを呼び出して、4k個のブロックサイズで出力のtmpfileにコピーすることで機能します。次に、関数はホールドループを設定します。これは、単一の完全なサイクルであってもほとんど完了しないはずです-関数がそのstdinをファイルに置き換える前に、ddが少なくとも1つのファイルへの書き込みを確実に行うためです。記述子はtmpfileにリンクされ、その後すぐにrmでファイルのリンクを解除します。これにより、関数はトラップやクリーンアップを必要とせずにストリームを確実に処理できます。関数がfdで解放するとすぐに、名前付きのファイルシステムリンクのみがすでに削除されているため、tmpfileは存在しなくなります。

2
mikeserv

頭と尾を使用する必要があるとおっしゃっていましたが、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はより効率的になります。

2
Falsenames

次のような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

この場合、これは少しやり過ぎですが、フィルターが大きくなると、恩恵を受けることができます。

0
mkalkov