Захват стандарта из хвоста -f "следовать" - PullRequest
5 голосов
/ 19 июля 2011

Я пытаюсь захватить вывод из 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 для включения повторных попыток, она, кажется, работает правильно после того, как пришло сообщение о плохом дескрипторе файла, и она пытается открыть файл во второй раз.

Возможно, я мог бы попробовать более свежую сборку win32 из репозитория coreutils git?

1 Ответ

3 голосов
/ 08 февраля 2012

Я знаю, что это не совсем то, что вы спрашиваете, но, как говорит Джеймс в комментариях, вы можете сделать эквивалентную функциональность непосредственно в 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);
                  });
...