清空文件而不中断管道对其的写入


12

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

./my_app > log

我想不时地(按需)清除(即清空)日志,并尝试了诸如

cat "" > log

但是,似乎总是会破坏原始管道,并且程序不再将其输出重定向到日志文件。

有什么办法吗?

更新资料

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


这就是为什么您通常使用日志记录守护程序来记录事物的原因……
Kiwy 2014年

@Kiwy您能详细说明如何解决该问题吗?
bangnab 2014年

很好,您通常使用日志守护程序或让您的应用处理日志,因为将内容写到输出并重定向它并不可靠。您应该看看syslogdlogrotate
Kiwy 2014年

2
如果执行./my_app >> log(强制附加)并cp /dev/null log截断它,事情是否起作用?
Mark Plotnick 2014年

1
您收到什么错误消息?您看到什么行为?“不再将其输出重定向到日志文件了”不是很明确。另外,由于没有名为的文件,cat "" > log因此不是有效的cat命令""
Mikel 2014年

Answers:


13

此问题的另一种形式发生在日志定期循环运行的长时间运行的应用程序中。即使您移动原始日志(例如mv log.txt log.1),并在实际记录发生之前立即将其替换为同名文件,但是如果该进程将文件保持打开状态,则最终要么写入log.1(因为这可能仍然是开放的inode)或什么都不做。

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

这是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

还在做。

不幸的是,您无法在shell脚本中为已执行的子进程执行此操作,因为如果它在前台,则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的独立过程。要清除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仍在运行,并且正在记录其输出。在这种情况下,无需修改应用程序。


感谢您的解释。但是,就我而言,我无法修改应用程序以实施您的解决方案。
bangnab 2014年

2
如果您无法在应用程序中实现信号处理程序(因为您无法修改周期),则可以使用此技术通过信号陷阱传递日志-请参阅“ However ...”
goldilocks

好的,我会尝试一下,让您知道它的运行情况。
bangnab 2014年

我终于有C语言编写的这个CLI应用程序(对不起它花了一点时间比原定): cognitivedissonance.ca/cogware/pipelog
金发姑娘

6

TL; DR

追加模式下打开日志文件:

cmd >> log

然后,您可以使用以下方法安全地截断它:

: > log

细节

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

在前两个中,内核会记住您当前的位置(我是说,打开文件描述,由所有复制或继承了该文件描述符的文件描述符共享,这些文件描述符是通过从打开文件的分支中派生出来的)文件。

当您这样做时:

cmd > log

log由shell以只写模式打开的stdout cmd

cmd(由外壳程序和所有可能的子代生成的初始过程)在写入其stdout时,在它们在该文件上共享的打开文件描述所保持的当前光标位置处进行写入。

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

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

: > log

然后cmd写入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

这意味着您无法截断以只读模式打开的文件(对于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

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

在最新版本的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_APPEND是0x400或八进制02000)

所以外壳的>>打开与文件O_WRONLY|O_APPEND(和0100000这里是O_LARGEFILE这是不相关的这个问题),同时>O_WRONLY唯一的(并且<>O_RDWR唯一的)。

如果您执行以下操作:

sudo lsof -nP +f g | grep ,AP

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


为什么在中使用:(冒号): >
mvorisek

1
@Mvorisek,这将重定向不产生任何输出的命令的输出::。如果没有命令,则行为在外壳之间会有所不同。
斯特凡Chazelas

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


我希望能够按需轮换。这实际上是在自动化测试期间生成的日志,我想在运行测试之前清除它。
bangnab 2014年

0

syslog(在所有变体中)早已解决了这个问题,但是有两种工具可以用最少的精力解决您的特定问题。

第一个功能更轻便,功能更差的解决方案是记录器(任何管理员工具箱都必须具有)。这是一个简单的实用程序,可将标准输入复制到syslog。(通过降压,使文件旋转成为logrotate和syslog的问题)

第二种更优雅但又不太便携的解决方案是syslog-ng,它除了接受来自标准syslog套接字的日志消息外,还可以执行程序,这些程序的输出通过记录器进行过滤。(我尚未使用此功能,但是它对于您想要做的事情看起来很完美。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.