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

Posted

技术标签:

【中文标题】在使用文件时截断文件 (Linux)【英文标题】:Truncating a file while it's being used (Linux) 【发布时间】:2010-11-02 02:10:51 【问题描述】:

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

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

cp /dev/null file

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

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

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

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

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

【问题讨论】:

【参考方案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

【讨论】:

【参考方案2】:

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

理想情况下,您应该要求应用程序供应商打开带有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);

我对它的测试有点随意,但勉强足以让我相信它有效。


更简单的选择

Billy 在他的answer 中指出,'&gt;&gt;' 是附加运算符 - 实际上,在 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') 代码。这只是表明,当您将一种特定技术用于其他(有效)目的时,使其适应另一种技术不一定是最简单的机制——即使它有效。

【讨论】:

日志文件由我创建,通过重定向标准输出。有什么方法可以对通过简单重定向创建的文件执行相当于 O_APPEND 的操作? @Hobo:简明扼要 - 不。或者,至少,非常不可能。如果您能够在调试器下启动程序,您可能可以进行 fcntl() 系统调用以将 O_APPEND 属性添加到标准输出,但否则,我认为您会被卡住。好吧,让我们想一想……您可以在另一个应用程序周围编写一个包装器……它执行 fcntl() - 也可能是重定向 - 然后运行第 3 方程序。给我一点时间好好看看…… 哇。为花时间写这篇文章干杯。我认为这比现阶段我想做的要多——我想我会坚持拆分——但我真的很感谢你抽出时间。可惜我不能多次投票给你的答案;-) @Hobo:只需使用 >> 而不是 > 重定向标准输出,如@Billy 的回答中所述。 感谢您的回答。我在使用复制/截断日志文件进行轮换时遇到了这个问题,起初我不明白为什么文件会恢复到原来的逻辑大小。【参考方案3】:

我在 redhat v6 上遇到过类似问题,echo &gt; file&gt; file 导致 apache 和 tomcat 出现故障,因为它们无法访问日志文件。

而且修复很奇怪

echo " " > file

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

【讨论】:

也为我工作。你拯救了我的一天【参考方案4】:

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

所以最终结果是:myprogram &gt;&gt; myprogram.log 2&gt;&amp;1 &amp;

【讨论】:

【参考方案5】:

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

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

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

我跑了

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

在 1.7 GB 的日志文件上。

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

【讨论】:

这是命令行选项吗?还是 C 函数?手册页似乎暗示后者。并不是说我反对编写包装器,只是为了我的理解。 C 函数 truncate() 一直存在(对于 always 的某些值)。从 coreutils 7.0 开始,还有一个程序 truncate,大概是包装了 C 函数。 谢谢,它有帮助。我一直在尝试在不停止的情况下清除 tomcat 日志并且它可以工作。 它真的适用于 Java 应用程序吗?对我来说不是!即使truncate -s 0 GC.log 在几秒钟内显示“截断”文件,在这段时间之后(我猜 - 当应用程序写入更多时) - 文件恢复到原来的大小。【参考方案8】:

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

【讨论】:

【参考方案9】:

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

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

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

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

【讨论】:

您可能需要 LD_PRELOAD 而不是 LD_LIBRARY_PATH。我认为你的解释是正确的,虽然细节有点模糊 - 请参阅我的解释(可能会有同样的指责)。 感谢您澄清发生这种情况的原因。我认为它是这样的,但很高兴得到证实。我认为在这种情况下拦截系统调用比我想做的要多,但感谢您提供另一种选择。【参考方案10】:

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

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

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

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

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

【讨论】:

干杯。是的,我认为这就是我要采取的方法。我将使用 Michiel 的使用 split 的建议。我没有意识到它可以在标准输入和文件(我应该有)上工作。【参考方案11】:

试试&gt; file


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

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

【讨论】:

通过文件重定向似乎不起作用 - 它与我在问题中提到的“cp /dev/null 文件”具有相同的问题。它似乎可以工作,但是下次将文件写入它时,它会恢复到以前的大小。在我的 linux 风格中,该特定命令返回“无效的空命令”。不确定您是否建议“[something] > file”,而不仅仅是“> file”。 我的意思是'>文件',也许它是一个特定于 bash 的命令。文件恢复是相当奇怪的。 我也看到过这种行为。似乎文件指针正在更改为一个新的空文件。但是正在运行的进程对旧文件有句柄。当它写入时,它会将文件指针更改回旧文件。【参考方案12】:

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

kill -HUP [进程 ID]

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

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

【讨论】:

我不认为我想重新启动进程(启动速度很慢,这会影响我们的用户),但是为教我有关 kill -HUP 的知识而欢呼 - 我不是意识到这一点。 @Hobo:澄清一下,对于识别 HUP 的守护进程,'kill -HUP' 告诉他们重新读取他们的配置和/或关闭并重新打开他们正在使用的任何文件。它不会重新启动该过程。 (如果守护进程没有捕获 HUP,那么,是的,它会正常死掉......)【参考方案13】:

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

【讨论】:

非常好 - 谢谢。看起来是我想做的最好的选择。 这是选择的解决方案似乎很奇怪。它对减小文件大小没有任何作用;它所做的只是为您提供文件各部分的副本以及原始文件。 是的,这是我需要的,而不是我要求的。我将通过拆分重定向,而不是直接重定向到日志文件,这将限制单个日志文件的大小。 对于那些摸不着头脑的人,这意味着什么:$your_command | split --bytes 100M - 前缀 投反对票。需要更多详细的答案才能达到如此高的质量。

以上是关于在使用文件时截断文件 (Linux)的主要内容,如果未能解决你的问题,请参考以下文章

使用 BufferedInputStream 读取大文件时 Java 文件 IO 被截断

如何在读取时检测文件已被截断

PHP文件包含

为啥 URL 在存储然后从本地文件系统检索时会被截断?

低版本ie保存文件时,文件名过长被截断

上传文件时截断 Web 套接字块