然而...

然而...

我有一个程序,其输出重定向到日志文件:

./my_app > log

我想不时(按需)清除(即清空)日志并尝试各种方法,例如

cat "" > log

然而,似乎原始管道随后被中断,并且程序不再将其输出重定向到日志文件。

有什么办法可以做到这一点吗?

更新

请注意,我无法修改生成输出的应用程序。它只是将其输出到标准输出,我想将其保存在日志中,以便我可以在需要时检查它,并在需要时清除它。但是我不需要重新启动应用程序。

答案1

此问题的另一种形式发生在日志定期轮换的长时间运行的应用程序中。即使您移动原始日志(例如,mv log.txt log.1)并在任何实际日志记录发生之前立即将其替换为同名文件,如果进程保持文件打开,它最终也会写入log.1(因为这可能仍然是打开的索引节点)或什么也不做。

处理此问题的常见方法(系统记录器本身以这种方式工作)是在进程中实现一个信号处理程序,该处理程序将关闭并重新打开其日志。然后,当您想要移动或清除(通过删除)日志时,请立即将该信号发送到进程。

这是 bash 的一个简单演示——请原谅我蹩脚的 shell 技能(但如果您要编辑此内容以获得最佳实践等,请确保您首先了解其功能并测试您的修订版)你编辑):

#!/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

仍在继续。

不幸的是,你不能在 shell 脚本中为已执行的子进程执行此操作,因为如果它位于前台,bash 自己的信号处理程序 ( traps) 将被挂起,如果你将其分叉到后台,则无法重新分配它的信号处理程序。输出。即,这是您必须在应用程序中实现的东西。

然而...

如果您无法修改应用程序(例如,因为您没有编写它),我有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 的独立进程。要清除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, 仍在运行并且正在记录其输出。在这种情况下,不需要对应用程序进行任何修改。

答案2

长话短说

打开您的日志文件附加模式:

cmd >> log

然后,您可以安全地截断它:

: > log

细节

使用类似 Bourne 的 shell,可以通过 3 种主要方式打开文件进行写入。在只写( >),读+写<>) 或者附加(和只写>>)模式。

在前两个中,内核会记住您当前的位置(我的意思是,打开文件描述,由所有通过从打开该文件的文件描述符复制或继承的文件描述符共享)都进入该文件。

当你这样做时:

cmd > log

log开放于只写shell 的标准输出模式cmd

cmd(它的初始进程由 shell 和所有可能的子进程生成)在写入标准输出时,在当前光标位置写入打开文件描述他们共享该文件。

例如,如果cmd最初写入zzz,则该位置将位于文件中的字节偏移量 4 处,并且下一次cmd或其子级写入该文件时,无论文件在时间间隔内是否增长或收缩,数据都将被写入到该位置。

如果文件已缩小,例如,如果文件已被截断

: > log

cmdwrites xx,这些xx将被写入 offset 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

这意味着您无法截断以只写模式打开的文件(对于读+写)就像你这样做一样,在文件上打开文件描述符的进程将在文件的开头留下 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

这也更安全,就像两个进程错误地打开(以这种方式)文件一样(例如,如果您启动了同一守护进程的两个实例),它们的输出不会互相覆盖。

在最新版本的 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

这些标志对应于..._ 传递给open系统调用的标志。

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

O_APPEND是 0x400 或八进制 02000)

因此,外壳程序>>打开文件时使用O_WRONLY|O_APPEND(这里的 0100000 是 O_LARGEFILE ,与这个问题无关) while >is O_WRONLYonly (并且<>O_RDWRonly)。

如果您执行以下操作:

sudo lsof -nP +f g | grep ,AP

要搜索使用 打开的文件O_APPEND,您会发现大多数当前在系统上打开以进行写入的日志文件。

答案3

如果我理解正确,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

答案4

这个问题早已通过 syslog(及其所有变体)解决了,但是有两种工具可以轻松解决您的特定问题。

第一个更便携但通用性较差的解决方案是记录器(任何管理员工具箱都必须具备)。它是一个简单的实用程序,可将标准输入复制到系统日志。 (推卸责任,让文件轮转成为logrotate和syslog的问题)

第二种更优雅但可移植性较差的解决方案是 syslog-ng,它除了接受来自标准 syslog 套接字的日志消息之外,还可以执行通过记录器过滤输出的程序。 (我还没有使用过这个功能,但它看起来非常适合你想做的事情。)

相关内容