C# непрерывно читать файл
Я хочу читать файл непрерывно, как хвост GNU с параметром "-f". Мне нужно это для чтения в реальном времени файла журнала. Как правильно это сделать?
7 ответов
Вы хотите открыть 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
метод так часто, как вы хотите. Вы можете немного оптимизировать ситуацию, открыв файл только один раз в начале программы. Это зависит от вас.
Более естественный подход к использованию 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
используется только для пробуждения основного цикла чтения.
Чтобы непрерывно следить за хвостом файла, вам просто нужно помнить длину файла до.
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. Для меня достаточно таймера, и самое большее, он проверяет только длину файла каждую секунду, если файл не изменился.
Это мое решение
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}");
}
Вы можете использовать класс FileSystemWatcher, который может отправлять уведомления о различных событиях, происходящих в файловой системе, таких как изменение файла.
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);
}
Если вы просто ищете инструмент для этого, попробуйте бесплатную версию Bare tail