web-dev-qa-db-ja.com

ログファイルの最後のn行だけをどのように保持しますか?

私が作成したスクリプトは何かを行い、最後に、独自のログファイルにいくつかの行を追加します。ログファイルの最後のn行(たとえば、1000行)のみを保持したいと思います。これは、スクリプトの最後で次のように実行できます。

tail -n 1000 myscript.log > myscript.log.tmp
mv -f myscript.log.tmp myscript.log

しかし、よりクリーンでエレガントなソリューションはありますか?たぶん、単一のコマンドを介して達成?

23
dr_

これは可能ですが、他の人が言ったように、最も安全なオプションは、新しいファイルを生成してから、そのファイルを移動して元のファイルを上書きすることです。

以下のメソッドは行をBASHにロードするため、tailからの行数に応じて、ログ行の内容を格納するためのローカルシェルのメモリ使用量に影響します。

以下は、空行がログファイルの最後に存在する場合にも削除します(BASHが"$(tail -1000 test.log)"を評価する動作のため)ので、すべてのシナリオで本当に100%正確な切り捨てが行われませんが、状況は十分かもしれません。

$ wc -l myscript.log
475494 myscript.log

$ echo "$(tail -1000 myscript.log)" > myscript.log

$ wc -l myscript.log
1000 myscript.log
29
parkamark

ユーティリティspongeは、このケースのためだけに設計されています。あなたがそれをインストールしているなら、あなたの2行は書くことができます:

tail -n 1000 myscript.log | sponge myscript.log

通常、ファイルへの書き込みと同時にファイルから読み取ることは信頼できません。 spongeは、tailが読み取りを完了してパイプを終了するまでmyscript.logに書き込まないことで、これを解決します。

インストール

Debianのようなシステムにspongeをインストールするには:

apt-get install moreutils

RHEL/CentOSシステムにspongeをインストールするには、EPELリポジトリを追加して、次のようにします。

yum install moreutils

ドキュメンテーション

man spongeから:

spongeは標準入力を読み取り、それを指定されたファイルに書き込みます。シェルリダイレクトとは異なり、spongeはすべての入力を吸収してから出力ファイルを書き込みます。これにより、同じファイルを読み書きするパイプラインを構築できます。

24
John1024

間違いなく「tail + mv」の方がはるかに優れています。しかしgnu sedの場合は、

sed -i -e :a -e '$q;N;101,$D;ba' log
5
JJoao

記録として、edを使用すると、次のようなことができます

ed -s infile <<\IN
0r !tail -n 1000 infile
+1,$d
,p
q
IN

これは、tail -n 1000 infileの出力でinfilereadsを開き(つまり、最初の行の前にその出力を挿入し)、最初の行からファイルの終わりまで削除します。ファイルをインプレース編集するには、,pwに置き換えます。
ただし、edソリューションは大きなファイルには適していません。

3
don_crissti

別のプロセスがログファイルを更新していて、それによる干渉を避けたい場合は、2番目のファイルを保持するのが最善の策です。 2番目のファイル(「アーカイブファイル」と呼びます)は、最後に記録されたn行を保持します。

これをできるだけきれいに行うには、まず既存のログファイルを一時的な場所に移動します。次に、それを以前に保存したアーカイブファイルと組み合わせて、n行まで保持します。

ロギングしているプロセスについては、次回ログメッセージを書き込むときに新しいファイルを開始するだけです。

#!/bin/bash

log_dir=/path/to/my/logs
log_filename=my_file.log
lines_to_keep=1000
archive_filename="${log_filename}.001"

work_dir="$(mktemp -d -p "$logdir")" && \
    touch "$log_dir/$archive_filename" && \
    touch "$log_dir/$log_filename" && \
    mv "$log_dir/$archive_filename" "$work_dir/" && \
    mv "$log_dir/$log_filename" "$work_dir/" && \
    cat "$work_dir/$archive_filename" "$work_dir/$log_filename" | \
        tail -n $lines_to_keep > "$log_dir/$archive_filename" && \
    rm "$work_dir/$archive_filename" && \
    rm "$work_dir/$log_filename" && \
    rmdir "$work_dir"
0
Bryan Roach

スクリプトで実行できることは、ログローテーションのロジックを実装することです。関数を介してすべてのロギングを実行します。

log()
{
   ...
}

この関数は、最初に、次のようなことを行います。

printf "%s\n" "$*" >> logfile

次に、ファイルのサイズを確認するか、ファイルをローテーションする必要があると判断します。その時点で、ファイルlogfile.1が存在する場合は削除され、ファイルlogfile.0が存在する場合はlogfile.1に名前が変更され、logfilelogfile.0に名前変更されます。

回転するかどうかの決定は、スクリプト自体に保持されているカウンターに基づくことができます。 1000に達すると、ゼロにリセットされます。

常に厳密に1000行にトリミングすることが要件である場合、スクリプトは開始時にログファイルの行数をカウントし、それに応じてカウンターを初期化できます(またはカウントが既に1000以上の場合は、すぐにローテーションを実行します)。

または、wc -c logfileなどを使用してサイズを取得し、特定のサイズを超えることに基づいてローテーションを行うこともできます。この方法では、状態を判断するためにファイルをスキャンする必要はありません。

0
Kaz

mvの代わりにcpコマンドを使用して、ソフトウェアが実行されている場所にログファイルを配置できるようにしました。たぶん、別のユーザーのホームディレクトリまたはアプリディレクトリにあり、すべてのログを1つの場所にハードリンクとして持っています。 mvコマンドを使用すると、ハードリンクが失われます。代わりにcpコマンドを使用すると、このハードリンクが保持されます。

私のコードは次のようなものです:

TMP_FILE="$(mktemp "${TMPFILENAME}.XXX")"

for FILE in "${LOGFILE_DIR}"/* ; do
    tail -n $MAXLINES "${FILE}" > "${TMP_FILE}"
    if [ $(ls -g "${TMP_FILE}" | awk '{print $4}') -lt $(ls -g "${FILE}" | awk '{print $4}') ] ; then
        cp "${TMP_FILE}" "${FILE}"
    fi
done   

したがって、ファイルが同じファイルシステム上にある場合、ユーザーにいくつかの異なる権限を与えることができ、${LOGFILE_DIR}では、私と同じように長さを変更します。

mvコマンドの場合、ファイル間のハードリンクが失われるため、2番目のファイルは最初のファイルに接続されなくなります。おそらく別の場所に配置されます。

他の場所では、誰かがファイルを消去することを許可しない場合、ログは一緒に残り、独自のスクリプトで制御されます。

logrotate多分もっと良いです。しかし、私はこの解決策に満足しています。

「」に邪魔されることはありませんが、私の場合、スペースやその他の特殊文字が含まれているファイルがいくつかあります。

たとえば、古いファイルがOLDFILE.Zipに自動圧縮されたDirがあり、圧縮されたものはすべてファイル.Zip_logにもリストされているため、.Zip_logもこのDirにありますが、 LOGFILE_DIRは私が持っています:

ln .Zip_log "${LOGFILE_DIR}/USER_Zip_log"

ハードリンクであるため、同等のファイル。

0
Andreas Bartels