2010-09-24 7 views

ответ

21

Вы хотите открыть FileStream в двоичном режиме. Периодически ищите конец файла минус 1024 байта (или что-то еще), затем читайте до конца и выводите. Вот как работает tail -f.

Ответы на вопросы:

Binary, потому что это трудно случайно получить доступ к файлу, если вы читаете это как текст. Вы должны сами выполнить преобразование двоичного текста, но это не сложно. (См. Ниже)

1024 байта, потому что это приятное удобное число и должно обрабатывать 10 или 15 строк текста. Как правило.

Вот пример открытия файла, чтение последних 1024 байта, и преобразования его в текст:

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

Заметим, что вы должны открыть с FileShare.ReadWrite, так как вы пытаетесь прочитать файл, который в настоящее время открыт для записи другим процессом.

Также обратите внимание, что я использовал Encoding.Default, который в США/английском и для большинства западноевропейских языков будет представлять собой 8-разрядную кодировку символов. Если файл написан в какой-либо другой кодировке (например, UTF-8 или другой кодировке Unicode), возможно, что байты не будут правильно преобразовываться в символы. Вам придется обработать это, определив кодировку, если вы считаете, что это будет проблемой. Поиск переполнения стека для информации об определении текстовой кодировки файла.

Если вы хотите делать это периодически (например, каждые 15 секунд), вы можете настроить таймер, который вызывает метод ReadTail так часто, как вы хотите. Вы можете немного оптимизировать ситуацию, открыв файл только один раз в начале программы. Это зависит от вас.

+0

Почему 1024? Почему бинарный? Пример? –

+0

@Dmytro: Смотрите мое обновленное сообщение. –

+0

Если с момента последней проверки было записано более 1024 байтов, не пропустили бы эти данные некоторые данные или дважды прочитали данные? – baruch

2

Вы можете использовать класс FileSystemWatcher, который может отправлять уведомления о различных событиях, происходящих в файловой системе, таких как файл, измененный.

+1

OK. Предположим, я получил событие, которое изменил файл. Как я могу прочитать до конца с позиции, которую я закончил, прочитав в прошлый раз? Пример Eny оценили :) –

-4

Если вы просто ищете инструмент, чтобы сделать это, а затем проверить бесплатную версию Bare tail

+4

Нет, я не ищу инструмент. Мне нужен способ реализовать его в C# для моей программы. –

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

Более естественный подход использования 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(); 

Здесь главный цикл чтения перестает ждать поступающих данных и FileSystemWatcher используется только будить основной цикл чтения.

+1

Вы должны сделать: 'fsw.EnableRaisingEvents = true;' перед запуском события 'fsw.Changed' –

+0

Да, вы правы, моя ошибка. Приведенный выше код работает только из-за тайм-аута 'WaitOne'. – tsul

+1

@MadsY Исправлен ответ, как вы предложили. – tsul

3

Чтобы постоянно контролировать хвост файла, вам просто нужно запомнить длину файла раньше.

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

мне пришлось использовать ASCIIEncoding, потому что этот код не достаточно умен, чтобы удовлетворить переменную длину символов в UTF8 на границах буфера.

Примечание: Вы можете изменить часть Thread.Sleep на разные тайминги, а также вы можете связать ее с файловым и блокирующим шаблоном - Monitor.Enter/Wait/Pulse. Для меня достаточно таймера и, самое большее, он проверяет длину файла каждую секунду, если файл не изменился.

1

Это мое решение

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

так, в вашем коде вы можете сделать

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