2011-07-18 17 views
5

Estoy tratando de capturar el resultado de cola en el modo follow, donde muestra el texto ya que detecta cambios en la longitud del archivo, particularmente útil para seguir archivos de registro a medida que se agregan líneas . Por algún motivo, mi llamada a StandardOutput.Read() está bloqueando hasta que tail.exe finalice por completo.Capturando estándar desde tail -f "follow"

relevante ejemplo de código:

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(); 

También he intentado usar el soporte para el controlador de OutputDataReceived evento que presenta el mismo comportamiento de bloqueo:

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

tengo un poco más de código por todas partes la llamada a StandardOutput.Read(), pero esto simplifica el ejemplo y aún muestra el comportamiento de bloqueo indeseable. ¿Hay algo más que pueda hacer para permitir que mi código reaccione ante la disponibilidad de datos en la transmisión StandardOutput antes de que salga la aplicación hija?

¿Es esto simplemente una peculiaridad de cómo se ejecuta tail.exe? Estoy usando la versión 2.0 compilada como parte del paquete UnxUtils.

Actualización: esto parece estar al menos parcialmente relacionado con peculiaridades en tail.exe. Agarré el archivo binario del proyecto GnuWin32 como parte del paquete CoreUtils y la versión aumentó a 5.3.0. Si utilizo la opción -f para seguir sin reintentos, aparece el temido "descriptor de archivo incorrecto" en STDERR (fácil de ignorar) y el proceso finaliza inmediatamente. Si utilizo la opción -F para incluir reintentos, parece que funciona correctamente después de que ha aparecido el mensaje de descriptor de archivo incorrecto e intenta abrir el archivo por segunda vez.

¿Hay quizás una compilación de win32 más reciente del repositorio de git coreutils que podría probar?

+0

"tail-f" simplemente comprueba el archivo de cada unidades x tiempo y, si es crecido, lee las cosas nuevas. No sé C#, pero me imagino que sería fácil de implementar allí. Puede obtener inspiración del código fuente para una versión de código abierto de cola (como GNU). Puede ser más fácil implementar la funcionalidad que necesita en C# que resolver este problema. – theglauber

+1

¿Ha considerado usar simplemente FileSystemWatcher o Timer y almacenar una posición de secuencia para búsquedas rápidas usando StreamReader en lugar de ejecutar otro ejecutable? – JamieSee

Respuesta

3

Sé que no es exageradamente lo que estás preguntando, pero como dice James en los comentarios, puedes hacer la funcionalidad equivalente directamente en C# para evitar tener que iniciar otro proceso.

Una manera de hacerlo es la siguiente:

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(); 
    } 
} 

Luego de llamar por ejemplo:

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

Gracias por la respuesta: su código es bastante bueno. De hecho, probé algo similar, pero esto no funciona con los archivos que están actualmente en uso. tail.exe todavía leerá archivos bloqueados mientras que FileStream no lo hará, al menos no con opciones que he podido determinar. – Goyuix

+1

Creo que resolví el problema de bloqueo: debe especificar FileShare.ReadWrite, lo que significa permitir otros procesos readwrite access (http://msdn.microsoft.com/en-us/library/system.io.fileshare.aspx) . He actualizado mi respuesta con este cambio y noté algunas rarezas con el FileSystemWatcher, así que simplemente lo conecté en un temporizador para que verifique el archivo cada 500 ms e informe cualquier cosa nueva. – kmp