C# Textdatei parsen, restrukturieren und ausgeben

psi24

Cadet 4th Year
Registriert
Dez. 2012
Beiträge
70
Hallo zusammen,

Ich hab ein Problem beim bearbeiten einer Textdatei.

Die Datei (bei mir natürlich ohne Zeilenumbrüche vorliegend) enthält ip Felder, und sollen später in einer art Filter verwendet werden, wofür ich die datei etwas aufbereiten muss. Erstes etappenziel ist das entfernen von Kommentaren und Leerzeilen aller Art, sowie htaccess Befehle wie "deny from".

Zeile 3 löst eine ArgumentOutOfRangeException aus:

Code:
                            if (sReader.ReadLine().StartsWith("# ") ||
                                sReader.ReadLine().StartsWith(" ") ||
                                char.IsLetter(sReader.ReadLine(), 0))
                            {
                                continue;
                            }

Zu der Ausnahme und StreamReadern hab ich bisher aber nix passendes gefunden.

Eigentlich soll das Programm alle Kommentare (bisauf mit $ gekennzeichnete) und frei Zeilen oder solche, die mit einem Buchstaben beginnen herausfiltern. Das richtige parsen und entfalten der IP-Felder in eine Liste muss erst noch im Kopf geplant werden.

Programmcode:
Code:
#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Diagnostics;
#endregion

namespace ipFileParser
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                MessageBox.Show("Programm über Befehlszeile mit Eingabedatei als Parameter starten!\n Beispiel: ipFileParser ips.txt", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
            else
            {
                if ( File.Exists(args[0]) )
                {
                    List<String> input = new List<string>();
                    List<string> output = new List<string>();
                    using (StreamReader sReader = new StreamReader(args[0]))
                    {
#if DEBUG
                        int totLines = 0;
                        int numLines = 0;
                        string tmp;
#endif
                        string buf;
                        while ( sReader.Peek() >= 0)    //  still reading
                        {
#if DEBUG
                            totLines++;
#endif

                            if (sReader.ReadLine().StartsWith("# ") ||
                                sReader.ReadLine().StartsWith(" ") ||
                                char.IsLetter(sReader.ReadLine(), 0))
                            {
                                continue;
                            }
#if DEBUG 
                            numLines++;
#endif
                            buf = sReader.ReadLine();
                            if (buf.StartsWith("deny from "))
                            {
                                buf.Substring(0, 10);   //  remove "deny from "
                            }
                            output.Add(buf);
                        }

                        File.WriteAllLines(Application.StartupPath + "\\output.txt", output.ToArray());

#if DEBUG
                            tmp = string.Format("{0} lines read, {1} lines skipped, {2} lines pushed to output", totLines, totLines - numLines, numLines);
                            Debug.WriteLine(tmp);
#endif
                    }
                }
            }
        }
    }
}




IDE ist VS 2013
 

Anhänge

  • ipParser-error.PNG
    ipParser-error.PNG
    86,1 KB · Aufrufe: 423
Kenne mich mit C# jetzt nicht aus, aber wenn sReader.ReadLine() jeweils eine Zeile liest, werden in deinem IF-Statement im Worst-Case 3 Zeilen gelesen. Besser wäre es du speicherst das Ergebnis des Aufrufes zwischen und prüfst dann auf die Bedingungen.
 
Zeile 3 löst eine ArgumentOutOfRangeException aus:
Ich tippe mal darauf, dass "char.isletter()" an der Stelle eine Leere Zeichenkette serviert bekommt. Siehe dazu http://msdn.microsoft.com/de-de/library/zff1at55(v=vs.110).aspx

Mal abgesehen davon ist der if-Block mit den drei "ReadLine()" Aufrufen doch irgendwie doof. Jeder Aufruf ließt doch eine weitere Zeile ein oder sehe ich das falsch? Ich würde "buf = sReader.ReadLine();" an den Anfang der while Schleife verlegen und dann mit "buf" arbeiten, damit immer dieselbe Zeile getestet wird. Ansonsten überspringst du doch im schlimmsten Fall drei Zeilen!?
 
Ah, verdammt, es ist tatsächlich bei leeren Zeilen der Index. Danke, KillerCow :hammer_alt:

buf fungiert jetzt auch wirklich als Puffer.

Code:
                        while ( sReader.Peek() >= 0)    //  still reading
                        {
                            buf = sReader.ReadLine();
#if DEBUG
                            totLines++;
#endif

                            if (buf.Length > 0)
                            {
                                if (buf.StartsWith("# ") || buf.StartsWith(" ") || char.IsLetter(buf, 0))
                                {
                                    continue;
                                }
#if DEBUG 
                            numLines++;
#endif

                                if (buf.StartsWith("deny from "))
                                {
                                    buf.Substring(0, 10);   //  remove "deny from "
                                }
                                output.Add(buf); 
                            }
                        }
Code:
char.IsLetter(buf, 0)'
sortiert jetzt leider die ganzen ips mit aus, da muss ich mir was neues überlegen. Wäre genial, wenn es eine art Rastererfassung geben würde, die Formate im Stream aufspührt...

nnn.nnn.nnn.nnn/nn z.B. :(
 
Wie geil ist das denn???:love:

Also, wenn ich das richtig verstanden habe, müsste
Code:
\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/d{1,2}\b
alles zurückliefern was so aussieht:

{1-3 Ziffern}.{1-3 Ziffern}.{1-3 Ziffern}.{1-3 Ziffern}/{1-2 Ziffern} ?
 
Echt, die C# Keule für so was Kleines? Geht in PowerShell wunderbar kurz und bündig (je nach Komfort und Umfang natürlich):
Code:
Param(
    [ValidateScript({ Test-Path -PathType Leaf $_ })]
    [Parameter( Position = 0 )]
    [Alias('Input')]
    [string]$InputFilename,
    [Parameter( Position = 1 )]
    [Alias('Output')]
    [string]$OutputFilename
)

$Contents = @()

$IpRegex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}\b"

switch -Regex -File $InputFilename
{
    '^(deny|allow) from\b'
    {
        switch( $_ )
        {
            {$_ -match $IpRegex} { ([Regex]$IpRegex).Matches( $_ ) | % { $Contents += $_.Value } }
            default { $Contents += $_ }
        }
    }
    '^#\$' { $Contents += $_ }
    '^$' { $Contents += $_ }
    # Whitelist anstatt ^[a-z]
    '^order' { $Contents += $_ }
}

if( ![string]::IsNullOrEmpty( $OutputFilename ) ) { $Contents > $OutputFilename }
else { $Contents }
Aufruf via
Code:
script.ps1 input.txt
script.ps1 input.txt output.txt
script.ps1 input.txt > output.txt
script.ps1 -Input input.txt
script.ps1 -Input input.txt -Output output.txt
script.ps1 -InputFilename input.txt -OutputFilename output.txt 
// usw.
In deiner Datei stören übrigens Zeilen wie
Code:
To also block Nigerian traffic copy/paste these deny statements after the order and allow statements in your .htaccess file:
Die hab ich jetzt mal mit rausgefiltert (ergo ne Whitelist anstatt "^[a-z]").

Wenn man nur die IPs und/oder #$-Kommentare filtern wöllte, könnte man wunderbar auch nen Ein-/Zweizeiler hinknallen.
 
Warum denn nicht? Ich lerne C# zur Zeit, wie eine Art Hobby.
Im übrigen kann ich mit Powershell gar nix anfangen :freak:

Von daher weiß ich nicht, ob ich dein Script richtig ausgeführt habe
 

Anhänge

  • Unbenannt.PNG
    Unbenannt.PNG
    46,2 KB · Aufrufe: 224
psi24 schrieb:
Warum denn nicht? Ich lerne C# zur Zeit, wie eine Art Hobby.
Dann sag das doch. Allerdings ist das imho nicht wirklich brauchbar, wenn du mit ein paar FileStreams herumhandtierst. Da gibt es bedeutend Besseres zum Erlernen der Sprache und gerade C# mit .NET bietet dir hierbei enorm viel (async und (P)LINQ um nur mal zwei zu nennen). Neben dem ganzen "OOP-Zeugs" natürlich...

Oder lernst du gerade eher wie manprogrammiert und nimmst dafür C#?
psi24 schrieb:
Im übrigen kann ich mit Powershell gar nix anfangen :freak:
Ist ebenso .NET, nur viel einfacher.
psi24 schrieb:
Von daher weiß ich nicht, ob ich dein Script richtig ausgeführt habe
Code:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
ausführen (als Admin), dann kannst du lokale Skripte ausführen.
 
Super, hat geklappt. Vielen Dank!

Yuuri schrieb:
Oder lernst du gerade eher wie manprogrammiert und nimmst dafür C#?

Beides, hab mit C++ so die basics gelernt und als es mit Klassen los ging, hab ich auf C# umgesattelt und nochmal alles von vorne angefangen. Ich finde es einfach angenehmer mit C# zu arbeiten.
Deshalb werde ich an dem Progarmm weiter feilen und basteln.

Trotzdem vielen Dank, für das Script und die generell rege Beteiligung :daumen:


Tut mir leid für den bump, aber ein generelle Frage:

Welche Methode ist für eine solche Aufgabe (Datei Zeile für Zeile lesen, Zeilen prüfen und eventuell Daten aus ihr lesen) die bestmöglichste Lösung? Im Bezug auf Geschwindigkeit, Performanz und allein unter logischem Aspekt?

Mit einer List<String>, einem StreamReader, oder noch einen andere Alternative?
MSDN sagt für Text: List<String>, für andere Datein mit text aber auch StreamReader???
 
Zuletzt bearbeitet:
Welche Methode ist für eine solche Aufgabe (Datei Zeile für Zeile lesen, Zeilen prüfen und eventuell Daten aus ihr lesen) die bestmöglichste Lösung? Im Bezug auf Geschwindigkeit, Performanz und allein unter logischem Aspekt?

Du hast doch die Lösung eigentlich schon:
Mit der StreamReader-Klasse hast du alle Möglichkeiten, Dateien zeichenbasiert auszulesen (Zeichen für Zeichen, Zeile für Zeile oder alles auf einmal). In deinem Beispiel ist es richtig, Zeile für Zeile einzulesen, da du dann deine Daten auch zeilenbasiert in eine Collection werfen kannst (Array, List, Dictionary etc.).

Mit einer List<String>, einem StreamReader, oder noch einen andere Alternative?
MSDN sagt für Text: List<String>, für andere Datein mit text aber auch StreamReader???

Der StreamReader hat eigentlich nichts mit dem Collection-Objekt zu tun, in welches du deine Daten speicherst.
Du nutzt den StreamReader für den Zugriff auf die Datei und brauchst dann ja irgendein Datenhaltungs-Objekt für die Daten, um später easy darauf zugreifen zu können.
Hier ein bisschen Hintergrundwissen zu den Collection-Klassen: http://openbook.galileo-press.de/visual_csharp_2012/1997_08_001.html
 
Zurück
Oben