私が作成したスクリプトは何かを行い、最後に、独自のログファイルにいくつかの行を追加します。ログファイルの最後のn行(たとえば、1000行)のみを保持したいと思います。これは、スクリプトの最後で次のように実行できます。
tail -n 1000 myscript.log > myscript.log.tmp
mv -f myscript.log.tmp myscript.log
しかし、よりクリーンでエレガントなソリューションはありますか?たぶん、単一のコマンドを介して達成?
これは可能ですが、他の人が言ったように、最も安全なオプションは、新しいファイルを生成してから、そのファイルを移動して元のファイルを上書きすることです。
以下のメソッドは行を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
ユーティリティ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
はすべての入力を吸収してから出力ファイルを書き込みます。これにより、同じファイルを読み書きするパイプラインを構築できます。
間違いなく「tail + mv」の方がはるかに優れています。しかしgnu sedの場合は、
sed -i -e :a -e '$q;N;101,$D;ba' log
記録として、ed
を使用すると、次のようなことができます
ed -s infile <<\IN
0r !tail -n 1000 infile
+1,$d
,p
q
IN
これは、tail -n 1000 infile
の出力でinfile
とr
eadsを開き(つまり、最初の行の前にその出力を挿入し)、最初の行からファイルの終わりまで削除します。ファイルをインプレース編集するには、,p
をw
に置き換えます。
ただし、ed
ソリューションは大きなファイルには適していません。
別のプロセスがログファイルを更新していて、それによる干渉を避けたい場合は、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"
スクリプトで実行できることは、ログローテーションのロジックを実装することです。関数を介してすべてのロギングを実行します。
log()
{
...
}
この関数は、最初に、次のようなことを行います。
printf "%s\n" "$*" >> logfile
次に、ファイルのサイズを確認するか、ファイルをローテーションする必要があると判断します。その時点で、ファイルlogfile.1
が存在する場合は削除され、ファイルlogfile.0
が存在する場合はlogfile.1
に名前が変更され、logfile
がlogfile.0
に名前変更されます。
回転するかどうかの決定は、スクリプト自体に保持されているカウンターに基づくことができます。 1000に達すると、ゼロにリセットされます。
常に厳密に1000行にトリミングすることが要件である場合、スクリプトは開始時にログファイルの行数をカウントし、それに応じてカウンターを初期化できます(またはカウントが既に1000以上の場合は、すぐにローテーションを実行します)。
または、wc -c logfile
などを使用してサイズを取得し、特定のサイズを超えることに基づいてローテーションを行うこともできます。この方法では、状態を判断するためにファイルをスキャンする必要はありません。
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"
ハードリンクであるため、同等のファイル。