40

我有一个将大量数据写入标准输出的过程,我将其重定向到日志文件。我想通过偶尔将当前文件复制到一个新名称并截断它来限制文件的大小。

我截断文件的常用技术,比如

cp /dev/null file

不工作,大概是因为进程正在使用它。

有什么方法可以截断文件吗?或者删除它并以某种方式将进程的标准输出与新文件相关联?

FWIW,这是我无法修改以更改其日志记录模型的第三方产品。

编辑重定向文件似乎与上面的副本有相同的问题 - 文件在下次写入时恢复到以前的大小:

ls -l sample.log ; echo > sample.log ; ls -l sample.log ; sleep 10 ; ls -l sample.log
-rw-rw-r-- 1 user group 1291999 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1292311 Jun 11  2009 sample.log
4

13 回答 13

33

从 coreutils 7.0 开始,有一个truncate命令。

于 2010-07-16T11:03:40.250 回答
32

这些重新增长的文件的有趣之处在于,在您通过复制文件截断文件后,前 128 KB 左右将全为零/dev/null。发生这种情况是因为文件被截断为零长度,但应用程序中的文件描述符在最后一次写入后仍立即指向。当它再次写入时,文件系统将文件的开头视为所有零字节 - 而不实际将零写入磁盘。

理想情况下,您应该要求应用程序的供应商打开带有O_APPEND标志的日志文件。这意味着在您截断文件后,下一次写入将隐式查找文件末尾(意味着回到偏移量零),然后写入新信息。


此代码装配标准输出,使其处于O_APPEND模式,然后调用由其参数给出的命令(就像nice在调整其 nice 级别后nohup运行命令,或者在修复问题后运行命令以忽略 SIGHUP)。

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

static char *arg0 = "<unknown>";

static void error(const char *fmt, ...)
{
    va_list args;
    int errnum = errno;
    fprintf(stderr, "%s: ", arg0);
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    putc('\n', stderr);
    fflush(0);
    exit(1);
}

int main(int argc, char **argv)
{
    int attr;
    arg0 = argv[0];

    if (argc < 2)
        error("Usage: %s cmd [arg ...]", arg0);
    if ((attr = fcntl(1, F_GETFL, &attr)) < 0)
        error("fcntl(F_GETFL) failed");
    attr |= O_APPEND;
    if (fcntl(1, F_SETFL, attr) != 0)
        error("fcntl(F_SETFL) failed");
    execvp(argv[1], &argv[1]);
    error("failed to exec %s", argv[1]);
    return(1);
}

我对它的测试有点随意,但仅足以说服我它有效。


更简单的选择

比利在他的回答中指出 ' >>' 是附加运算符 - 事实上,在 Solaris 10 上,bash(版本 3.00.16(1))确实使用了该O_APPEND标志 - 从而使上面的代码变得不必要,如图所示('Black JL:'是我在这台机器上的提示):

Black JL: truss -o bash.truss bash -c "echo Hi >> x3.29"
Black JL: grep open bash.truss
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
open("/usr/lib/libcurses.so.1", O_RDONLY)       = 3
open("/usr/lib/libsocket.so.1", O_RDONLY)       = 3
open("/usr/lib/libnsl.so.1", O_RDONLY)          = 3
open("/usr/lib/libdl.so.1", O_RDONLY)           = 3
open("/usr/lib/libc.so.1", O_RDONLY)            = 3
open("/platform/SUNW,Ultra-4/lib/libc_psr.so.1", O_RDONLY) = 3
open64("/dev/tty", O_RDWR|O_NONBLOCK)           = 3
stat64("/usr/openssl/v0.9.8e/bin/bash", 0xFFBFF2A8) Err#2 ENOENT
open64("x3.29", O_WRONLY|O_APPEND|O_CREAT, 0666) = 3
Black JL:

使用附加重定向而不是上面的包装器(' cantrip ')代码。这只是表明,当您将一种特定技术用于其他(有效)目的时,使其适应另一种技术不一定是最简单的机制——即使它有效。

于 2009-06-12T02:40:32.627 回答
14

使用 >> 而不是 > 重定向输出。这将允许您截断文件而不会使文件恢复到原始大小。另外,不要忘记重定向 STDERR (2>&1)。

所以最终的结果是:myprogram >> myprogram.log 2>&1 &

于 2013-05-23T17:46:49.547 回答
10

试试> file


关于评论的更新:它对我很有效:

robert@rm:~> echo "content" > test-file
robert@rm:~> cat test-file 
content
robert@rm:~> > test-file
robert@rm:~> cat test-file 
于 2009-06-11T09:58:54.007 回答
7

我在 redhat v6 上遇到了类似的问题,echo > file或者> file导致 apache 和 tomcat 出现故障,因为它们无法访问日志文件。

修复很奇怪

echo " " > file

会清理文件,不会造成任何问题。

于 2014-06-25T12:07:51.810 回答
6

在 Linux(实际上是所有 unicies)中,文件在打开时创建,在没有任何引用时删除。在这种情况下,打开它的程序和它在“in”中打开的目录包含对该文件的引用。当 cp 程序想要写入文件时,它会从目录中获取对它的引用,将长度为零的值写入存储在目录中的元数据中(这是稍微简化了一点)并放弃了句柄。然后原始程序仍然持有原始文件句柄,将更多数据写入文件并保存它认为长度应该是什么。

即使您从目录中删除文件的位置,程序仍会继续向其中写入数据(并耗尽磁盘空间),即使没有其他程序可以引用它。

简而言之,一旦程序对文件有引用(句柄),您所做的任何事情都不会改变它。

理论上有一些方法可以通过设置 LD_LIBRARY_PATH 来包含一个拦截所有文件访问系统调用的程序来修改程序的行为。我记得在某个地方看到过类似的东西,但记不起名字了。

于 2009-06-12T02:15:41.267 回答
5

查看实用程序split(1),它是 GNU Coreutils 的一部分。

于 2009-06-11T10:00:52.067 回答
3

在使用该文件时,如果您尝试将其无效或类似的东西,有时它可能会“混淆”正在写入日志文件的应用程序,之后它可能不会记录任何内容。

我尝试做的是为该日志设置一种代理/过滤器,而不是重定向到文件,重定向到进程或将获取输入并写入滚动文件的东西。

也许它可以通过脚本来完成,否则你可以为此编写一个简单的应用程序(java或其他东西)。对应用程序性能的影响应该很小,但您必须运行一些测试。

顺便说一句,您的应用程序是独立的网络应用程序,...?也许还有其他选择需要调查。

编辑:还有一个我个人从未使用过的附加重定向运算符>> ,但它可能不会锁定文件。

于 2009-06-11T10:34:43.540 回答
3

我下载并编译了最新的coreutils,所以我可以truncate使用。

跑了./configuremake但是没有跑make install

所有已编译的实用程序都出现在“src”文件夹中。

我跑了

[path]/src/truncate -s 1024000 textfileineedtotruncate.log

在 1.7 GB 的日志文件上。

它没有改变使用时列出的大小ls -l,但它确实释放了所有磁盘空间——这是我在/var填满并终止进程之前真正需要做的。

感谢您提供有关“截断”的提示!

于 2011-06-20T18:30:03.857 回答
2

您是否检查过任何信号(如 SIGHUP)对第三方产品的行为,以查看它是否会开始记录新文件?首先,您将旧文件移动到永久名称。

杀死 -HUP [进程 ID]

然后它会再次开始写出来。

或者(如 Billy 建议的那样)可能会将应用程序的输出重定向到日志程序,如 multilog 或 Apache 常用的日志程序,称为 cronolog。然后,在将所有内容写入初始文件描述符(文件)之前,您将对所有内容的去向进行更细粒度的控制,这就是它的全部内容。

于 2009-06-11T10:40:47.613 回答
2

与其将其重定向到文件,不如将其通过管道传送到一个程序,该程序通过关闭文件、移动文件并在每次文件变得太大时打开一个新文件来自动旋转文件。

于 2009-06-12T17:18:31.580 回答
2

@Hobo 使用freopen(),它重用流来打开文件名指定的文件或更改其访问模式。如果指定了新文件名,该函数首先尝试关闭任何已与流(第三个参数)关联的文件并解除关联。然后,与该流是否成功关闭无关,freopen 打开由文件名指定的文件并将其与流相关联,就像 fopen 使用指定模式所做的那样。

如果第三方二进制文件正在生成日志,我们需要编写一个包装器来旋转日志,第三方将在 proxyrun 线程中运行,如下所示。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>

using namespace std;

extern "C" void * proxyrun(void * pArg){
   static int lsiLineNum = 0;
   while(1) 
   {
     printf("\nLOGGER: %d",++lsiLineNum);
     fflush(stdout);
   }
  return NULL;
}


int main(int argc, char **argv)
{
  pthread_t lThdId;
  if(0 != pthread_create(&lThdId, NULL, proxyrun, NULL))
  {
    return 1;
  }

  char lpcFileName[256] = {0,};

  static int x = 0;

  while(1)
  {
    printf("\n<<<MAIN SLEEP>>>");
    fflush(stdout);
    sprintf(lpcFileName, "/home/yogesh/C++TestPrograms/std.txt%d",++x);
    freopen(lpcFileName,"w",stdout);
    sleep(10);
  }

  return 0;
}
于 2012-11-05T10:29:21.113 回答
1

我遇到了类似的问题,无法对从 cron 运行的脚本的输出执行“tail -f”:

    * * * * * my_script >> /var/log/my_script.log 2>&1

我通过更改 stderr 重定向来修复它:

    * * * * * my_script >> /var/log/my_script.log 2>/var/log/my_script.err
于 2016-11-27T06:23:09.707 回答