Question search for string in a text file and output the entire line to another text file

matisse

New member
Joined
Jan 15, 2013
Messages
4
Programming Experience
Beginner
I have a text file with variable content such as:
#notes, description of file contents
mode = full
descriptive data values
Registry=http://myhostname.domain.local
#additional notes here
...
...
file configuration options


I need to be able to find the line beginning with Registry and append the contents to a separate text file.
Here is what I have so far but I am getting a Null Reference Exception with the approach I am taking with the search.
I would greatly appreciate any help I can get.


Module Module1
Sub Main()
Dim outputFile As String = "C:\VB\testingoutput.txt"
Dim objWriter As New System.IO.StreamWriter(outputFile, True)
Dim strSearch = ("Registry")
Dim list As New List(Of String)
Using r As IO.StreamReader = New IO.StreamReader("C:\VB\test.txt")
Dim linestr As String
linestr = r.ReadLine
Do While (Not linestr Is Nothing)
list.Add(linestr)
Console.WriteLine(linestr)
linestr = r.ReadLine
Loop
If linestr.StartsWith(strSearch) Then
objWriter.WriteLine(linestr)
End If
End Using
End Sub
End Module
 
At which point of code do you get this error? I use to get 'Null Reference' when some variable that should contain an instance of some class remains with value of 'Nothing', hence it fails when executing a method or function.
Although Intellisense from VS.IDE accepts writing class' methods and functions from that class, that is just because variable is typed. But the actual class may be not there when executing, because of some reason (usually that is my own logic mistake) that prevents the very instance of the class of being created or stored in that particular variable.
 
Thank you for the response. This is the line that gave me the error: If linestr.StartsWith(strSearch) Then. I tried making multiple changes to get around it and ended up with the below code. I can see that it does read the first line of the file but it doesn't seem to loop through the file and do the search.

Imports System.IO
Module Module1
Public Sub Main()
Dim outputFile As String = "C:\Test\testingoutput.txt"
Dim readFile As String = "C:\Test\test.txt"
Dim objWriter As New System.IO.StreamWriter(outputFile, True)
Dim strSearch As String = "Registry"
Using reader As StreamReader = New StreamReader(readFile)
Dim linestr As String
linestr = reader.ReadLine
Console.WriteLine(linestr)
Console.WriteLine("Please Press any key to continue!")
Console.ReadKey()
Do While (linestr IsNot Nothing)
If linestr.StartsWith(strSearch) Then
objWriter.WriteLine(linestr)
Console.WriteLine(linestr)
End If
Loop
End Using
objWriter.Close()
End Sub
End Module
 
I think reader.ReadLine is a tricky statement.
It concentrates three different operations under a single statement:
- first, it tries reading and storing the next line of text starting from reader's pointer;
- second, if it succeeded reading a line, it moves reader's pointer to immediately after the line that was read;
- third, it returns the previously read line as a string, or Nothing if there was no remaining line to be read.
So, it seems that the collection of reader.ReadLine response must be inside the loop, and the variable that collects the line must be tested against null reference (that is, against containing Nothing) both for its further use (in your case, for searching some particular expression) and for deciding wether to continue looping.
With that in mind, I mended your code as below, and it seems to work:
Imports System.IO
Module Module1
    Sub Main()
        Dim outputFile As String = "D:\testingoutput.txt"
        Dim readFile As String = "D:\test.txt"
        Dim objWriter As New System.IO.StreamWriter(outputFile, True)
        Dim strSearch As String = "Registry"
        Using reader As StreamReader = New StreamReader(readFile)
            Dim linestr As String
            Do
                linestr = reader.ReadLine
                If linestr IsNot Nothing Then
                    Console.WriteLine("Read: " & linestr)
                    If linestr.StartsWith(strSearch) Then
                        objWriter.WriteLine(linestr)
                        Console.WriteLine(linestr)
                        Console.WriteLine("      = line accepted. Please Press any key to continue!")
                    Else
                        Console.WriteLine("      = line rejected. Please Press any key to continue!")
                    End If
                    Console.ReadKey()
                End If
            Loop Until linestr Is Nothing
        End Using
        objWriter.Close()
    End Sub
End Module

I didn't want to mess up with your basic logic.
Perhaps I'd rather:
- first read all lines - into a List(Of String), for instance;
- then close the reader;
- then filter the read content (using an iteration like "For Each... Next" or, even better, a synthetic Linq query);
- and only then write its content.
As in this example:

    Sub Main()
        Dim outputFile As String = "D:\testingoutput.txt"
        Dim readFile As String = "D:\test.txt"
        Dim strSearch As String = "Registry"
        Dim lines As New List(Of String)
        Using reader As StreamReader = New StreamReader(readFile)
            Do
                lines.Add(reader.ReadLine)
            Loop Until lines.Last Is Nothing
            reader.Close()
        End Using
        Dim selectedlines = From line In lines Where line IsNot Nothing AndAlso line.StartsWith(strSearch) Select line
        Using objWriter As StreamWriter = New StreamWriter(outputFile, True)
            For Each line In selectedlines
                objWriter.WriteLine(line)
            Next
            objWriter.Close()
        End Using
    End Sub

But this is a question of logic fashion, I think, rather than a better or worse way of accomplishing things.
 
Last edited:
VBobCat, thank you for the elucidation regarding the reader.ReadLine and the code suggestions. I have played with it quite a bit today and it is working perfectly. I will continue experimenting with the alternate methods as you suggested.
 
You're welcome. Please have in mind that I'm just learning, like you. There are guys here with thousands of posts and thousands of reputation points, they are in the know ;-)
As I suspected, the code I proposed before does deserve some polish.
After I answered, I started thinking on my old days of writing code for the dBase III command interpreter, and remembered that pointer-based loop queries on a stack of records relied on the EOF (End-of-file) alert to know where to stop. Well, the StreamReader.ReadLine operation is very much alike a pointer-based query, so, I thought, perhaps it might have its own EOF signal. And it has, indeed.
Testing the property StreamReader.EndOfStream will:
- save us one idle loop cycle at the end of the lines;
- prevent that our container gets bogus data (that is, a string that actually evaluates to Nothing);
- prevent an error in case of source file has zero length, so no attempts of reading a line will happen in this case.
So I rewrote it this way:
    Sub Main()
        ' Step 1 - declare input variables
        Dim outputFile As String = "D:\testingoutput.txt"
        Dim readFile As String = "D:\test.txt"
        Dim strSearch As String = "Registry"
        '
        ' Step 2a create an object that works as multiple-string container
        Dim lines As New List(Of String)
        ' Step 2b read the lines of text from .txt file and store them into the container
        Using reader As StreamReader = New StreamReader(readFile)
            Do Until reader.EndOfStream
                lines.Add(reader.ReadLine)
            Loop
            reader.Close()
        End Using
        '
        ' Step 3 filter the read content into a second container (.NET will infer type IEnumerable(Of String) for it)
        ' - here I used LinQ, but one can accomplish with a "For... Next" iteration which nestes a "If... Endif" test
        Dim selectedlines = From line In lines Where line IsNot Nothing AndAlso line.StartsWith(strSearch) Select line
        '                
        ' Step 4 - iterate through filtered lines and write them to a second .txt file
        Using objWriter As StreamWriter = New StreamWriter(outputFile, True)
            For Each line In selectedlines
                objWriter.WriteLine(line)
            Next
            objWriter.Close()
        End Using
    End Sub

Besides, as a second thought, writing code along blocks that are well-defined and as independent as possible does have advantages, because in more complex applications you can reuse blocks that do things that are needed to be done in different sequences of logic.
In our case, illustrated below, we have these four blocks, 1-2-3-4. Suppose another routine in the same application would to a slightly different thing, such as reading all lines of a .txt file, reordering them and saving them in another file. We would have to write new code for sorting lines alphabetically, but we already have code for opening a file, reading its lines and saving lines to a .txt file. So our new routine would do the steps 1-2, then a new step, let's say number 5, and then 4 again.

That could be accomplished breaking code into functions that get their input in arguments and return its result, then we could do any of the operations, or both, without writing redundand code. Take a look on how, with the same code plus a single statement that sorts lines ("From line In lines Order By line Ascending Select line"), now we can do two different operations - selecting and sorting lines:

    Sub Main()
        Dim outputFile1 As String = "D:\testingoutput1.txt"
        Dim outputFile2 As String = "D:\testingoutput2.txt"
        Dim readFile As String = "D:\test.txt"
        Dim strSearch As String = "Registry"
        Dim lines As List(Of String) = ReadsLinesFromFile(readFile)
        Dim selectedlines = SelectsLines(lines, strSearch)
        Dim sortedlines = SortsLines(lines)
        SavesLinesToFile(outputFile1, selectedlines)
        SavesLinesToFile(outputFile2, sortedlines)
    End Sub
    Function ReadsLinesFromFile(readfile As String) As List(Of String)
        Dim lines As New List(Of String)
        Using reader As StreamReader = New StreamReader(readfile)
            Do Until reader.EndOfStream
                lines.Add(reader.ReadLine)
            Loop
            reader.Close()
        End Using
        Return lines
    End Function
    Function SelectsLines(lines As List(Of String), strSearch As String) As IEnumerable(Of String)
        Return From line In lines Where line IsNot Nothing AndAlso line.StartsWith(strSearch) Select line
    End Function
    Function SortsLines(lines As List(Of String)) As IEnumerable(Of String)
        Return From line In lines Order By line Ascending Select line
    End Function
    Sub SavesLinesToFile(outputFile As String, selectedlines As IEnumerable(Of String))
        Using objWriter As StreamWriter = New StreamWriter(outputFile, True)
            For Each line In selectedlines
                objWriter.WriteLine(line)
            Next
            objWriter.Close()
        End Using
    End Sub


Hope it helps you on further programming. Good Luck!
 
Thanks, VBobCat. I've been playing around with this yesterday and today. I see the elegance in your approach. This is something I had been considering as well; my next steps were to perform more advanced sorting and manipulation. Thanks again for your help!
 
Back
Top