捕获标准从尾部-f“跟随”

时间:2011-07-18 23:35:05

标签: c# .net-4.0 process stdout tail

我试图在跟随模式下捕获tail的输出,它在检测到文件长度变化时输出文本 - 特别适用于添加行时跟踪日志文件。出于某种原因,我对StandardOutput.Read()的调用是阻塞的,直到tail.exe完全退出。

相关代码示例:

var p = new Process() {
  StartInfo = new ProcessStartInfo("tail.exe") {
    UseShellExecute = false,
    RedirectStandardOutput = true,
    Arguments = "-f c:\\test.log"
  }
};
p.Start();

// the following thread blocks until the process exits
Task.Factory.StartNew(() => p.StandardOutput.Read());
// main thread wait until child process exits
p.WaitForExit();

我也尝试使用OutputDataReceived事件处理程序的支持,它表现出相同的阻塞行为:

p.OutputDataReceived += (proc, data) => {
  if (data != null && data.Data != null) {
    Console.WriteLine(data.Data);
  }
};
p.BeginOutputReadLine();

我对StandardOutput.Read()的调用有更多的代码,但这简化了示例并仍然表现出不良的阻塞行为。我还能做些什么来让我的代码在子应用程序退出之前对StandardOutput流中的数据可用性做出反应?

这可能是tail.exe运行方式的一个怪癖吗?我使用的是2.0版本,作为UnxUtils包的一部分编译。

更新:这似乎至少部分与tail.exe中的怪癖相关。我从GnuWin32项目中获取了二进制文件,作为CoreUtils包的一部分,版本升级到5.3.0。如果我在没有重试的情况下使用-f选项,我会在STDERR上遇到可怕的“坏文件描述符”问题(容易忽略),并且该过程立即终止。如果我使用-F选项来包含重试,则在出现错误的文件描述符消息之后它似乎正常工作并且它会尝试再次打开该文件。

我可以试试coreutils git存储库中是否有更新的win32版本?

1 个答案:

答案 0 :(得分:3)

我知道你问的并不是很好,但正如詹姆斯在评论中所说,你可以直接在c#中做同等的功能,以节省你必须启动另一个过程。

你能做到的一种方法是:

using System;
using System.IO;
using System.Text;
using System.Threading;

public class FollowingTail : IDisposable
{
    private readonly Stream _fileStream;
    private readonly Timer _timer;

    public FollowingTail(FileInfo file,
                         Encoding encoding,
                         Action<string> fileChanged)
    {

        _fileStream = new FileStream(file.FullName,
                                     FileMode.Open,
                                     FileAccess.Read,
                                     FileShare.ReadWrite);

        _timer = new Timer(o => CheckForUpdate(encoding, fileChanged),
                           null,
                           0,
                           500);
    }

    private void CheckForUpdate(Encoding encoding,
                                Action<string> fileChanged)
    {
        // Read the tail of the file off
        var tail = new StringBuilder();
        int read;
        var b = new byte[1024];
        while ((read = _fileStream.Read(b, 0, b.Length)) > 0)
        {
            tail.Append(encoding.GetString(b, 0, read));
        }

        // If we have anything notify the fileChanged callback
        // If we do not, make sure we are at the end
        if (tail.Length > 0)
        {
            fileChanged(tail.ToString());
        }
        else
        {
            _fileStream.Seek(0, SeekOrigin.End);
        }
    }

    // Not the best implementation if IDisposable but you get the idea
    // See http://msdn.microsoft.com/en-us/library/ms244737(v=vs.80).aspx
    // for how to do it properly
    public void Dispose()
    {
        _timer.Dispose();
        _fileStream.Dispose();
    }
}

然后打电话给例如:

new FollowingTail(new FileInfo(@"C:\test.log"),
                  Encoding.ASCII,
                  s =>
                  {
                      // Do something with the new stuff here, e.g. print it
                      Console.Write(s);
                  });