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


10

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

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

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

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


1
彼らは、特に使用するためにあなたを求めているheadtail?もしそうなら、あなたのソリューションはあなたがすることができるほとんど最高です。他のプログラムの使用が許可されている場合、sedまたはawkより優れたソリューションが許可されている場合(つまり、プロセス呼び出しが少ない場合)。
n.st 2015年

ええ、彼らは私たちに頭と尾を使うように求めています。お返事ありがとうございます。
user2709291 2015年

もう1つ追加できます>>。2つのコマンドを括弧で囲んで、連結された出力をリダイレクトすることにより、追加の出力リダイレクト()を回避できます(head -11 file | tail -6; head -24 file | tail -6) > file1。それは本当に良い個人的な好みに帰着します。
n.st 2015年

うまくいきますありがとうございます。ほんとうにありがとう。
user2709291 2015年

回答:


11

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

6

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

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

ファイルから一連の行を出力する最も簡単な方法は、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

2
動作することはありません。標準の指定された動作ですが、確かに、パイプは共有入力の信頼できる入力ソースではありませんユーティリティの説明のデフォルト標準のユーティリティがシーク可能な入力ファイルを読み取り、ファイルの終わりに達する前にエラーなしで終了すると、ユーティリティは、開いているファイルの説明のファイルオフセットが、ユーティリティ。
mikeserv 2015年

2

私はあなたが頭と尾を使う必要があると言ったのを知っていますが、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

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ためその反復の値がいるかどうかに応じて、$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 行をに、次の6 行をに出力/dev/nullstdoutます。引数の最後に到達し、-1繰り返しカウントを完全に循環した後、終了します。最初の引数があった場合は-2、プロセスをもう一度繰り返したか、または-できる限り長く繰り返していたでしょう。

argサイクルごとに、whileループが1回処理されます。各ループの先頭で、最初の行stdinがシェル変数に読み込まれます$l。はwhile head </dev/null; do :; done無期限に繰り返されるため、これは必要headです。ファイルの終わりに達したときに、その戻りを示します。したがって、EOFに対するチェックは専用でreadあり、2番目の引数が正の整数の場合にのみ、それに加えて改行printfが書き込まれ$lますstdout

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

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


0

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.