パイプへの書き込みを中断せずにファイルを空にする


12

出力がログファイルにリダイレクトされるプログラムがあります。

./my_app > log

時々ログをクリア(空)にして(オンデマンドで)、次のようなさまざまなことを試したいと思います。

cat "" > log

ただし、元のパイプは中断され、プログラムは出力をログファイルにリダイレクトしなくなります。

それを行う方法はありますか?

更新

出力を生成するアプリケーションを変更できないことに注意してください。それは標準出力に吐き出され、ログに保存して、必要なときに検査し、必要なときにクリアできるようにします。ただし、アプリケーションを再起動する必要はありません。


あなたが通常のものをログに記録するログ・デーモンを使用する理由...だこと
Kiwy

@Kiwyは、それがどのように問題を解決するかについて詳しく説明できますか?
バンナブ14

通常、ログデーモンを使用するか、アプリにログを処理させます。これは、出力への書き込みとリダイレクトが信頼できないためです。あなたは見てとるべきsyslogdlogrotate
Kiwy

2
./my_app >> log(強制的に追加するために)それcp /dev/null logを切り捨てると、物事は機能しますか?
マークPlotnick

1
どのようなエラーメッセージが表示されますか?どのような動作が見られますか?「出力をログファイルにリダイレクトしない」というのは、あまり具体的ではありません。また、というファイルcat "" > logがないため、有効なcatコマンドではありません""
ミケル14

回答:


13

この問題の別の形態は、ログが定期的にローテーションされる長時間実行されるアプリケーションで発生します。元のログ(例:)を移動して、mv log.txt log.1実際のロギングが発生する前に同じ名前のファイルですぐに置き換えても、プロセスがファイルを開いたままにしている場合、書き込みが行われますlog.1(まだオープンiノード)または何もない。

これに対処する一般的な方法(システムロガー自体はこの方法で動作します)は、ログを閉じて再度開くシグナルハンドラーをプロセスに実装することです。その後、ログを移動または消去(削除)する場合は、すぐにそのシグナルをプロセスに送信します。

bashの簡単なデモを次に示します-私の酷いシェルスキルは許してください(ただし、ベストプラクティスなどのためにこれを編集する場合は、まず機能を理解し、編集する前にリビジョンをテストしてください)。

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

バックグラウンドに分岐してこれを開始します。

> ./test.sh &
12356

PIDを端末に報告し、ログインを開始することに注意してくださいlog.txt。これで、2分間遊ぶことができます。数秒待ってから試してください:

> mv log.txt log.1 && kill -s 2 12356

kill -2 12356ここでも単純に機能する場合があります。シグナル2はSIGINTです(これはCtrl-Cの機能でもあるため、フォアグラウンドでこれを試して、ログファイルを別の端末から移動または削除できます)trap。チェックする;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

次に、log.txt移動してもまだ書き込み中かどうかを見てみましょう。

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

中断したところから右に向かっていることに注意してください。記録を残したくない場合は、ログを削除して消去します

> rm -f log.txt && kill -s 2 12356

小切手:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

まだ行っています。

残念ながら、実行されたサブプロセスのシェルスクリプトでこれを実行することはできません。フォアグラウンドにある場合、bashの独自のシグナルハンドラーtrapが中断され、バックグラウンドにフォークした場合、再割り当てできないためです。出力。つまり、これはアプリケーションに実装する必要があるものです。

しかしながら...

アプリケーションを変更できない場合(作成していないなど)、仲介として使用できるCLIユーティリティあります。また、ログへのパイプとして機能するスクリプトでこの簡単なバージョンを実装することもできます。

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

これを呼び出しましょうpipetrap.sh。次に、ログを記録するアプリケーションを模倣して、テストする別のプログラムが必要です。

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

それはtest.sh

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

これらは、個別のPIDを持つ2つの個別のプロセスです。test.shに集中しているの出力をクリアするにはpipetrap.sh

> rm -f log.txt && kill -s 2 15859

小切手:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858、test.shがまだ実行中であり、その出力がログに記録されています。この場合、アプリケーションを変更する必要はありません。


素敵な説明をありがとう。ただし、私の場合、アプリケーションを変更してソリューションを実装することはできません。
バンナブ14

2
アプリケーションにシグナルハンドラを実装できない場合(期間を変更できないため)、この手法を使用して、ログをシグナルトラップにパイプすることができます。「ただし...」の
goldilocks

[OK]を試してみて、それがどうなったかをお知らせします。
バンナブ

私はついにこれのためにCで書かれたCLIアプリを手に入れました(元々意図していたより少し時間がかかりました): cognitivedissonance.ca/cogware/pipelog
goldilocks 14

6

TL; DR

追加モードでログファイルを開きます。

cmd >> log

その後、次の方法で安全に切り捨てることができます。

: > log

詳細

Bourneのようなシェルでは、書き込み用にファイルを開くことができる主な方法が3つあります。書き込み専用>)、+読み書き<>)または追記(及び書き込みのみ>>)モード。

最初の2つでは、カーネルは、現在の位置(つまり、開いたファイルの説明を、ファイルを開いた場所からフォークすることで複製または継承したすべてのファイル記述で共有)を記憶しますファイル。

行うとき:

cmd > log

logのstdoutのためにシェルによって書き込み専用モードで開かれますcmd

cmd(シェルとすべての可能な子によって生成される初期プロセス)stdoutに書き込むとき、そのファイルで共有するオープンファイル記述が保持する現在のカーソル位置に書き込みます。

たとえば、cmd最初に書き込むzzz場合、位置はファイル内のバイトオフセット4になり、次回cmdまたはその子がファイルに書き込む場合、その間隔でファイルが成長したか縮小したかに関係なくデータが書き込まれます。

ファイルが縮小されている場合、たとえば、

: > log

そして、cmdwritesはxxxxoffset 4に書き込まれ、最初の3文字がNUL文字に置き換えられます。

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

つまり、書き込み専用モードで開かれたファイルを切り捨てることはできません(read + writeでも同じです)。ファイルでファイル記述子が開かれているプロセスは、ファイルの先頭にNUL文字を残します。ファイル(OS / Xを除き、通常はディスク上のスペースを使用しませんが、スパースファイルになります)。

代わりに(そして、ほとんどのアプリケーションがログファイルに書き込むときにそれを行うことに気付くでしょう)、追加モードでファイルを開く必要があります。

cmd >> log

または

: > log && cmd >> log

空のファイルで開始する場合。

追加モードでは、最後の書き込みがどこにあったかに関係なく、すべての書き込みはファイルの最後に行われます。

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

また、2つのプロセスが誤ってファイルを開いた場合(たとえば、同じデーモンの2つのインスタンスを起動した場合)、それらの出力は互いに上書きされないため、より安全です。

Linuxの最近のバージョンでは、現在の位置と、ファイル記述子が追加モードで開いているかどうかを確認することができます/proc/<pid>/fdinfo/<fd>

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

または:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

これらのフラグは、システムコールに渡されるO ..._ フラグに対応していopenます。

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

O_APPEND0x400または8進数02000です)

シェルのはそう>>でファイルを開いてO_WRONLY|O_APPENDいる間(および0100000ここでは、この質問には関係ありませんO_LARGEFILEがある)>であるO_WRONLYだけ(と<>あるO_RDWRのみ)。

あなたがする場合:

sudo lsof -nP +f g | grep ,AP

で開いているファイルを検索するにはO_APPEND、現在システム上で書き込み用に開いているほとんどのログファイルがあります。


なぜ:(コロン)を使用するの: > ですか?
mvorisek

1
@Mvorisek、出力を生成しないコマンドの出力をリダイレクトします::。コマンドがない場合、動作はシェルによって異なります。
ステファンシャゼラス

1

私が正しく理解している場合tee、合理的なアプローチのようです:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014

1

高速なソリューションとして、ログをローテーションで使用できます(たとえば、毎日ローテーション):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

ロギングをリダイレクトします ./my_app >> log$date.log


オンデマンドでローテーションできるようにしたいです。これは実際には自動テスト中に生成されるログであり、テストを実行する前にクリアしたいと思います。
バンナブ14

0

これは、syslog(すべての変形)で長い間解決されてきた問題ですが、最小限の労力で特定の問題を解決する2つのツールがあります。

最初の、より移植性が高く、汎用性の低いソリューションはロガーです(すべての管理者ツールボックスに必要です)。これは、標準入力をsyslogにコピーする単純なユーティリティです。(バックを渡し、ファイルのローテーションをlogrotateとsyslogの問題にします)

2番目の、よりエレガントだが移植性の低いソリューションはsyslog-ngです。これは、標準のsyslogソケットからのログメッセージの受け入れに加えて、出力がロガーでフィルタリングされるプログラムを実行できます。(私はまだこの機能を使用していませんが、あなたがやりたいことには完璧に見えます。)

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