2010-09-24 15 views

Odpowiedz

21

Chcesz otworzyć FileStream w trybie binarnym. Okresowo, szukaj końca pliku minus 1024 bajty (lub cokolwiek innego), a następnie odczytaj na końcu i na wyjściu. Tak działa tail -f.

Odpowiedzi na pytania:

Binary ponieważ trudno jest losowo dostępu do pliku, jeśli czytasz to jako tekst. Musisz samemu dokonać konwersji binarnej na tekst, ale nie jest to trudne. (Zobacz poniżej)

1024 bajtów, ponieważ jest to miły wygodny numer i powinien obsługiwać 10 lub 15 linii tekstu. Zazwyczaj.

Oto przykład otwierania pliku, czytając ostatnie 1024 bajtów i przekształcenie go do tekstu:

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

Pamiętaj, że musisz otworzyć z FileShare.ReadWrite, ponieważ próbujesz odczytać plik, który jest aktualnie otwarty do pisania innym procesem.

Należy również pamiętać, że użyłem Encoding.Default, który w języku angielskim/amerykańskim i dla większości zachodnioeuropejskich języków będzie oznaczał 8-bitowe znaki. Jeśli plik jest zapisany w innym kodowaniu (jak kodowanie UTF-8 lub inne kodowanie Unicode), możliwe, że bajty nie zostaną poprawnie przekonwertowane na znaki. Będziesz musiał sobie z tym poradzić, określając kodowanie, jeśli uważasz, że to będzie problem. Przeszukaj przepełnienie stosu w poszukiwaniu informacji o określaniu kodowania tekstu pliku.

Jeśli chcesz robić to okresowo (na przykład co 15 sekund), możesz ustawić zegar, który będzie wywoływał metodę ReadTail tak często, jak chcesz. Możesz trochę zoptymalizować, otwierając plik tylko raz na początku programu. To zależy od Ciebie.

+0

Dlaczego 1024? Dlaczego binnary? Przykład? –

+0

@Dmytro: Zobacz mój zaktualizowany post. –

+0

Jeśli od ostatniego sprawdzenia zapisano mniej lub więcej niż 1024 bajty, czy nie pominąłoby to niektórych danych lub dwóch danych? – baruch

2

Można użyć klasy FileSystemWatcher, która może wysyłać powiadomienia o różnych zdarzeniach zachodzących w systemie plików, takich jak zmieniony plik.

+1

OK. Powiedzmy, że otrzymałem zdarzenie, że plik został zmieniony. Jak mogę przeczytać do końca z pozycji, którą skończyłem czytać po raz ostatni? Przykład Eny doceniony :) –

-4

Jeśli tylko szukasz narzędzie to zrobić to sprawdź darmową wersję Bare tail

+4

Nie, nie szukam narzędzia. Potrzebuję sposobu, aby zaimplementować go w C# dla mojego programu. –

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

bardziej naturalne podejście z użyciem 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(); 

Tutaj główny cykl odczyt przestaje czekać na danych przychodzących i FileSystemWatcher służy tylko obudzić główny cykl odczytu.

+1

Musisz zrobić: 'fsw.EnableRaisingEvents = true;' przed wywołaniem zdarzenia 'fsw.Changed' –

+0

Tak, masz rację, mój błąd. Powyższy kod działa tylko z powodu limitu czasu 'WaitOne'. – tsul

+1

@MadsY Naprawiono odpowiedź zgodnie z propozycją. – tsul

3

Aby nieprzerwanie monitorować koniec pliku, wystarczy wcześniej zapamiętać długość pliku.

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

musiałem użyć ASCIIEncoding, ponieważ kod ten nie jest wystarczająco silny, aby zaspokoić zmiennych długościach charakter UTF8 na granicach buforowych.

Uwaga: Można zmienić pozycję Thread.Sleep na różne taktowania, a także można powiązać ją z mechanizmem tworzenia pliku i schematem blokowania - Monitor.Enter/Wait/Pulse. Dla mnie zegar wystarcza, a co najwyżej sprawdza długość pliku co sekundę, jeśli plik się nie zmienił.

1

To jest moje rozwiązanie

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

tak, to w kodzie można zrobić

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