web-dev-qa-db-ja.com

ファイルの最初の数行と最後の数行を表示するコマンド

多くの行を含むファイルがあり、各行の最初にタイムスタンプがあります。

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

したがって、私はこのログファイルから2つのことを頻繁にチェックします。

  1. 最初の数行には、グローバル条件と開始時刻も含まれています。
  2. 最後の数行は、他の情報と一緒に終了ステータスを持っています。

ファイルの最初と最後の数行だけを表示できる簡単なコマンドはありますか?

23
mtk

sedまたはawkを使用すると、1つのコマンドで作成できます。ただし、速度が落ちるので、sedおよびawkはとにかくファイル全体を実行する必要があります。速度の観点からは、関数を作成するか、毎回tail + headを組み合わせることをお勧めします。これには、入力がパイプの場合は機能しないという欠点がありますが、シェルがサポートしている場合は、プロセス置換を使用できます(以下の例を参照)。

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

そしてちょうどそれを起動します

first_last "/path/to/file_to_process"

プロセスの置換を続行するには(シェルのようなbash、zsh、kshのみ):

first_last <( command )

ps。 grepを追加して、「グローバル条件」が存在するかどうかを確認することもできます。

12
rush

@Rushは、head + tailを使用するのが適切ですが、大きなファイルの方が効率的ですが、小さなファイル(<20行)の場合、一部の行が2回出力されることがあります。

{ head; tail;} < /path/to/file

同様に効率的ですが、上記の問題はありません。

20

{ head; tail; }ソリューションはパイプ(またはソケットまたはその他のシーク不可能なファイル)では機能しません。これは、headがブロックによって読み取り、パイプからカーソルを離れる可能性があるため、データを大量に消費する可能性があるためです。 tailが選択するものを超えてファイル内。

したがって、シェルのreadのように一度に1文字ずつ読み取るツールを使用できます(ここでは、ヘッドラインとテールラインの数を引数として取る関数を使用しています)。

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

またはawkにtailを次のように実装します:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

sedの場合:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(ただし、一部のsed実装では、パターンスペースのサイズに低い制限があるため、テールラインの数が大きい場合は失敗します)。

9

bashプロセス置換を使用すると、次のことができます。

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

行が正しいとは限らないことに注意してください。ただし、約8kBを超えるファイルの場合は、そうなる可能性が非常に高くなります。この8kBのカットオフは読み取りバッファーの一般的なサイズであり、| {head; tail;}が小さなファイルに対して機能しない理由に関連しています。

headパイプラインを存続させるには、cat >/dev/nullが必要です。そうしないと、teeが早く終了し、tailから出力が得られますが、入力の最後ではなく、途中のどこかから出力されます。

最後に、tailを別の>/dev/nullに移動する代わりに、なぜ|を使用するのですか?次の場合:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headのstdoutは、コンソールではなくtailへのパイプに送られますが、これはまったく必要ありません。

4
Jander

edを使用すると(ファイル全体がRAMに読み込まれます)):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file
3
curx

引数を使用できるようにするための関数でのステファンの最初のソリューション(任意のBourne-likeまたはPOSIXシェルで動作します):

head_tail() {
    head "$@";
    tail "$@";
}

これでこれを行うことができます:

head_tail -n 5 < /path/to/file

もちろん、これは1つのファイルだけを見ていて、Stephaneのソリューションが(確実に)通常の(シーク可能な)ファイルでのみ機能することを前提としています。

2
l0b0

GNU sed-u--unbuffered)オプションを使用すると、sed -u 2qhead -n2のバッファなしの代替として使用できます。

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

最後の行がheadによって消費される入力のブロックの一部である場合、(head -n2;tail -n2)は失敗します。

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2
2
nisetama

Perlがインストールされている場合は、Perlを試すことができます。

Perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

これはほとんどのファイルで機能しますが、処理する前にファイル全体をメモリに読み込みます。 Perlスライスに慣れていない場合、角かっこ内の「0」は「最初の行を取る」を意味し、「-3 ...- 1」は「最後の3行を取る」ことを意味します。どちらもニーズに合わせて調整できます。本当に大きなファイルを処理する必要がある場合(「大きな」とは、RAMとスワップサイズに依存する場合があります)、次の方法を使用することをお勧めします。

Perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

毎回スライスになるため、多少遅くなる可能性がありますが、ファイルサイズには依存しません。

どちらのコマンドも、パイプ内と通常のファイルの両方で機能します。

1
Jasio

今日は、ストリームの前から最後の行と数行だけが必要なこのような状況に遭遇し、次のことを思いつきました。

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

私はこれを次のように読みます:最初の行の内容でホールドスペースを初期化し、ホールドスペースにライン2-3を追加します= EOFホールドスペースに最後の行を追加し、ホールドをスワップします- and-pattern space、およびpattern spaceを印刷します。

おそらく、私が持っているよりもsed- fuが多い人は、これを一般化して、この質問に示されているストリームの最後のfew行を出力する方法を理解できますが、私はそれを必要としませんでした。 sed$アドレスに基づいて、またはおそらくEOFのときに最後の数行のみが含まれるようにホールドスペースを管理することによって、数学を行う簡単な方法を見つけることができませんでした達した。

1
deaks