Je veux lire le fichier continuellement comme GNU tail avec "-f" param. J'en ai besoin pour lire en direct le fichier journal. Quelle est la bonne façon de le faire?
Vous voulez ouvrir une FileStream
en mode binaire. Périodiquement, cherchez jusqu'à la fin du fichier moins 1024 octets (ou quoi que ce soit), puis lisez-le jusqu'à la fin et obtenez-le en sortie. C'est comme ça que tail -f
fonctionne.
Réponses à vos questions:
Binaire, car il est difficile d'accéder au fichier de façon aléatoire si vous le lisez sous forme de texte. Vous devez faire la conversion binaire en texte vous-même, mais ce n'est pas difficile. (Voir ci-dessous)
1024 octets, car c’est un bon numéro commode et doit pouvoir traiter 10 à 15 lignes de texte. Habituellement.
Voici un exemple d'ouverture du fichier, de lecture des 1024 derniers octets et de conversion en texte:
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);
}
}
Notez que vous devez ouvrir avec FileShare.ReadWrite
, car vous essayez de lire un fichier en cours d'écriture par un autre processus.
Notez également que j’ai utilisé Encoding.Default
qui, en US/anglais et dans la plupart des langues d’Europe occidentale, sera un codage de caractères à 8 bits. Si le fichier est écrit dans un autre codage (comme UTF-8 ou un autre codage Unicode), il est possible que les octets ne soient pas convertis correctement en caractères. Vous devrez gérer cela en déterminant l'encodage si vous pensez que cela posera problème. La pile de recherche déborde d'informations sur la détermination du codage de texte d'un fichier.
Si vous souhaitez effectuer cela périodiquement (toutes les 15 secondes, par exemple), vous pouvez configurer une minuterie qui appelle la méthode ReadTail
aussi souvent que vous le souhaitez. Vous pouvez optimiser un peu les choses en ouvrant le fichier une seule fois au début du programme. C'est à toi de voir.
Approche plus naturelle d’utiliser 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();
Ici, le cycle de lecture principal cesse d'attendre les données entrantes et FileSystemWatcher
est utilisé uniquement pour réveiller le cycle de lecture principal.
Pour surveiller en permanence la fin du fichier, il vous suffit de vous rappeler la longueur du fichier avant.
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);
}
}
J'ai dû utiliser ASCIIEncoding, car ce code n'est pas assez intelligent pour prendre en charge les longueurs de caractères variables de UTF8 sur les limites de la mémoire tampon.
Remarque: vous pouvez modifier la partie Thread.Sleep en différents timings et vous pouvez également la lier avec un gestionnaire de fichiers et un modèle de blocage - Monitor.Enter/Wait/Pulse. Pour moi, le minuteur est suffisant et au plus, il ne vérifie la longueur du fichier que toutes les secondes, si le fichier n'a pas changé.
Vous pouvez utiliser la classe FileSystemWatcher qui peut envoyer des notifications pour différents événements se produisant sur le système de fichiers, comme le fichier modifié.
C'est ma solution
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);
}
}
}
alors, dans votre code, vous pouvez faire
foreach (string line in TailFrom(file))
{
Console.WriteLine($"line read= {line}");
}
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);
}