2010-09-24 27 views

Respuesta

21

que desea abrir un FileStream en modo binario. Periódicamente, busque el final del archivo menos 1024 bytes (o lo que sea), luego lea hasta el final y la salida. Así es como funciona el tail -f.

Las respuestas a sus preguntas:

binario porque es difícil acceder al azar el archivo si estás leyendo como texto. Tienes que hacer la conversión de binario a texto, pero no es difícil. (Consulte a continuación)

1024 bytes porque es un buen número conveniente, y debe manejar 10 o 15 líneas de texto. Generalmente.

He aquí un ejemplo de abrir el archivo, la lectura de los últimos 1024 bytes, y su conversión a texto:

static void ReadTail(string filename) 
{ 
    using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
    { 
     // Seek 1024 bytes from the end of the file 
     fs.Seek(-1024, SeekOrigin.End); 
     // read 1024 bytes 
     byte[] bytes = new byte[1024]; 
     fs.Read(bytes, 0, 1024); 
     // Convert bytes to string 
     string s = Encoding.Default.GetString(bytes); 
     // or string s = Encoding.UTF8.GetString(bytes); 
     // and output to console 
     Console.WriteLine(s); 
    } 
} 

Tenga en cuenta que debe abrir con FileShare.ReadWrite, ya que usted está tratando de leer un archivo que está actualmente abierto para escribir por otro proceso.

También tenga en cuenta que utilicé Encoding.Default, que en inglés/EE. UU. Y para la mayoría de los idiomas de Europa occidental será una codificación de caracteres de 8 bits. Si el archivo está escrito en alguna otra codificación (como UTF-8 u otra codificación Unicode), es posible que los bytes no se conviertan correctamente en caracteres. Tendrás que manejar eso determinando la codificación si crees que esto será un problema. Buscar desbordamiento de pila para obtener información sobre cómo determinar la codificación de texto de un archivo.

Si desea hacer esto periódicamente (cada 15 segundos, por ejemplo), puede configurar un temporizador que llame al método ReadTail tantas veces como desee. Puede optimizar un poco las cosas abriendo el archivo una sola vez al inicio del programa. Eso depende de usted. enfoque natural

+0

¿Por qué 1024? ¿Por qué binnary? Ejemplo? –

+0

@Dmytro: Ver mi publicación actualizada. –

+0

Si se escribieron más o menos de 1024 bytes desde el último control, ¿esto no omitirá algunos datos o leerá algunos datos dos veces? – baruch

2

Puede usar la clase FileSystemWatcher que puede enviar notificaciones de diferentes eventos que suceden en el sistema de archivos como el archivo modificado.

+1

OK. Digamos que recibí un evento que el archivo cambió. ¿Cómo puedo leer hasta el final desde la posición que terminé de leer la última vez? Ejemplo de Eny apreciado :) –

-4

Si sólo están buscando una herramienta para hacer esto, entonces echa un vistazo a la versión libre de Bare tail

+4

No, no estoy buscando una herramienta. Necesito una forma de implementarlo en C# para mi programa. –

0
private void button1_Click(object sender, EventArgs e) 
{ 
    if (folderBrowserDialog.ShowDialog() == DialogResult.OK) 
    { 
     path = folderBrowserDialog.SelectedPath; 
     fileSystemWatcher.Path = path; 

     string[] str = Directory.GetFiles(path); 
     string line; 
     fs = new FileStream(str[0], FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 
     tr = new StreamReader(fs); 

     while ((line = tr.ReadLine()) != null) 
     { 

      listBox.Items.Add(line); 
     } 


    } 
} 

private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e) 
{ 
    string line; 
    line = tr.ReadLine(); 
    listBox.Items.Add(line); 
} 
15

Más de utilizar FileSystemWatcher:

var wh = new AutoResetEvent(false); 
    var fsw = new FileSystemWatcher("."); 
    fsw.Filter = "file-to-read"; 
    fsw.EnableRaisingEvents = true; 
    fsw.Changed += (s,e) => wh.Set(); 

    var fs = new FileStream("file-to-read", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 
    using (var sr = new StreamReader(fs)) 
    { 
     var s = ""; 
     while (true) 
     { 
      s = sr.ReadLine(); 
      if (s != null) 
       Console.WriteLine(s); 
      else 
       wh.WaitOne(1000); 
     } 
    } 

    wh.Close(); 

Aquí el ciclo de lectura principal se detiene para esperar los datos entrantes y FileSystemWatcher se utiliza sólo para despertar el ciclo de lectura principal.

+1

Tienes que hacer: 'fsw.EnableRaisingEvents = true;' antes de que se active el evento 'fsw.Changed' –

+0

Sí, tienes razón, error mío. El código anterior solo funciona debido al tiempo de espera de 'WaitOne'. – tsul

+1

@MadsY Se corrigió la respuesta que usted había propuesto. – tsul

3

Para monitorear continuamente la cola del archivo, solo necesita recordar la longitud del archivo antes.

public static void MonitorTailOfFile(string filePath) 
{ 
    var initialFileSize = new FileInfo(filePath).Length; 
    var lastReadLength = initialFileSize - 1024; 
    if (lastReadLength < 0) lastReadLength = 0; 

    while (true) 
    { 
     try 
     { 
      var fileSize = new FileInfo(filePath).Length; 
      if (fileSize > lastReadLength) 
      { 
       using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
       { 
        fs.Seek(lastReadLength, SeekOrigin.Begin); 
        var buffer = new byte[1024]; 

        while (true) 
        { 
         var bytesRead = fs.Read(buffer, 0, buffer.Length); 
         lastReadLength += bytesRead; 

         if (bytesRead == 0) 
          break; 

         var text = ASCIIEncoding.ASCII.GetString(buffer, 0, bytesRead); 

         Console.Write(text); 
        } 
       } 
      } 
     } 
     catch { } 

     Thread.Sleep(1000); 
    } 
} 

tuve que usar ASCIIEncoding, ya que este código no es lo suficientemente inteligente como para atender a longitudes de caracteres variables de UTF8 en los límites de amortiguamiento.

Nota: Puede cambiar la parte Thread.Sleep para que tenga diferentes tiempos, y también puede vincularla con un vigilante de archivos y patrón de bloqueo - Monitor.Enter/Wait/Pulse. Para mí, el temporizador es suficiente, y como máximo solo verifica la longitud del archivo cada segundo, si el archivo no ha cambiado.

1

Esta es mi solución

static IEnumerable<string> TailFrom(string file) 
    { 
     using (var reader = File.OpenText(file)) 
     { 
      while (true) 
      { 
       string line = reader.ReadLine(); 
       if (reader.BaseStream.Length < reader.BaseStream.Position) 
        reader.BaseStream.Seek(0, SeekOrigin.Begin); 

       if (line != null) yield return line; 
       else Thread.Sleep(500); 
      } 
     } 
    } 

así, en su código que puede hacer

foreach (string line in TailFrom(file)) 
    { 
     Console.WriteLine($"line read= {line}");    
    }