Подтвердить что ты не робот

С# непрерывно читать файл

Я хочу постоянно читать файл, как хвост GNU, с параметром -f. Мне нужно это для чтения файла журнала. Каков правильный способ сделать это?

4b9b3361

Ответ 1

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

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

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

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 так часто, как вы хотите. Вы можете немного оптимизировать ситуацию, открыв файл только один раз в начале программы. Это вам.

Ответ 2

Более естественный подход использования 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 используется только для пробуждения основного цикла чтения.

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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