web-dev-qa-db-fra.com

c # lecture continue du fichier

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?

27
Dmytro Leonenko

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.

24
Jim Mischel

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.

22
tsul

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é.

3
Todd

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é.

2
Darin Dimitrov

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}");            
    }
1
Johann Medina
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);  
}
0
coolcake