在使用文件时截断文件(Linux)

时间:2009-06-11 09:55:58

标签: linux unix file logging truncate

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

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

cp /dev/null file

不起作用,大概是因为该过程正在使用它。

有什么方法可以截断文件吗?或者删除它并以某种方式将进程'stdout与新文件关联起来?

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

13 个答案:

答案 0 :(得分:31)

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

答案 1 :(得分:26)

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

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


此代码装配标准输出,因此它处于O_APPEND模式,然后调用其参数给出的命令(而非nice调整其良好级别后运行命令,或{{1}在修复之后运行命令,因此忽略了SIGHUP)。

nohup

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


更简单的替代

Billy在他的answer中注意到'#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))确实使用了{ {1}}标志 - 从而使上面的代码变得不必要,如图所示('Black JL:'是我在这台机器上的提示):

>>

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

答案 2 :(得分:12)

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

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

答案 3 :(得分:10)

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

答案 4 :(得分:8)

尝试> file


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

robert@rm:~> echo "content" > test-file
robert@rm:~> cat test-file 
content
robert@rm:~> > test-file
robert@rm:~> cat test-file 

答案 5 :(得分:6)

我在redhat v6上有类似的问题,echo > file> file导致apache和tomcat出错,因为日志文件对他们来说无法访问。

修复很奇怪

echo " " > file

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

答案 6 :(得分:3)

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

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

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

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

编辑:还有一个我个人从未使用的Append Redirection Operator >>,但它可能无法锁定该文件。

答案 7 :(得分:3)

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

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

简而言之,一旦程序对文件有一个引用(句柄),你所做的就是改变它。

理论上,通过将LD_LIBRARY_PATH设置为包含拦截所有文件访问系统调用的程序,可以修改程序行为。我记得在某个地方看到过这样的东西,虽然不记得这个名字。

答案 8 :(得分:3)

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

启用./configuremake,但未运行make install

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

我跑了

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

在1.7 GB的日志文件中。

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

感谢您提示“截断”!

答案 9 :(得分:2)

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

kill -HUP [process-id]

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

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

答案 10 :(得分:2)

@Hobo使用 freopen(),它重用流来打开filename指定的文件或更改其访问模式。 如果指定了新文件名,则该函数首先尝试关闭已与stream(第三个参数)关联的任何文件并取消关联。然后,无论该流是否成功关闭,freopen都会打开由filename指定的文件,并将其与流关联,就像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;
}

答案 11 :(得分:1)

而不是将其重定向到文件,您可以将其传输到一个程序,该程序通过关闭它,移动它并在每次变得太大时打开一个新文件来自动旋转文件。

答案 12 :(得分:1)

我遇到了类似的问题而无法做到&#34; tail -f&#34;在从cron运行的脚本的输出上:

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

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

    * * * * * my_script >> /var/log/my_script.log 2>/var/log/my_script.err