# Loop (read file contents)

Retrieves the lines in a text file, one at a time (performs better than FileReadLine).

    Loop, Read, InputFile , OutputFile

## Parameters

Read

  This parameter must be the word READ.

InputFile

  The name of the text file whose contents will be read by the loop, which is assumed to be in %A_WorkingDir% if an absolute path isn't specified. Windows and Unix formats are supported; that is, the file's lines may end in either carriage return and linefeed (`r`n) or just linefeed (`n).

OutputFile

  (Optional) The name of the file to be kept open for the duration of the loop, which is assumed to be in %A_WorkingDir% if an absolute path isn't specified.

  Within the loop's body, use the FileAppend command with only one parameter (the text to be written) to append to this special file. Appending to a file in this manner performs better than using FileAppend in its 2-parameter mode because the file does not need to be closed and re-opened for each operation. Remember to include a linefeed (`n) after the text, if desired.

  The file is not opened if nothing is ever written to it. This happens if the Loop performs zero iterations or if it never uses the FileAppend command.

  End of line (EOL) translation: To disable EOL translation, prepend an asterisk to the filename. This causes each linefeed character (`n) to be written as a single linefeed (LF) rather than the Windows standard of CR+LF. For example: `*C:\My Unix File.txt`. Even without the asterisk, EOL translation is disabled automatically if the Loop's first use of FileAppend writes any carriage return and linefeed pairs (`r`n).

  Standard Output (stdout): Specifying an asterisk (*) for OutputFile sends any text written by FileAppend to standard output (stdout). Although such output can be redirected to a file, piped to another EXE, or captured by fancy text editors, it will not appear at the command prompt it was launched from. See FileAppend for more details.

  Escaped Commas: Unlike the last parameter of most other commands, commas in OutputFile must be escaped (`,).

## Remarks

A file-reading loop is useful when you want to operate on each line contained in a text file, one at a time. It performs better than using FileReadLine because: 1) the file can be kept open for the entire operation; and 2) the file does not have to be re-scanned each time to find the requested line number.

The built-in variable A_LoopReadLine exists within any file-reading loop. It contains the contents of the current line excluding the carriage return and linefeed (`r`n) that marks the end of the line. If an inner file-reading loop is enclosed by an outer file-reading loop, the innermost loop's file-line will take precedence.

Lines up to 65,534 characters long can be read. If the length of a line exceeds this, its remaining characters will be read during the next loop iteration.

StringSplit or a parsing loop is often used inside a file-reading loop to parse the contents of each line retrieved from InputFile. For example, if InputFile's lines are each a series of tab-delimited fields, those fields can individually retrieved as in this example:

    Loop, read, C:\Database Export.txt
    {
        Loop, parse, A_LoopReadLine, %A_Tab%
        {
            MsgBox, Field number %A_Index% is %A_LoopField%.
        }
    }

To load an entire file into a variable, use FileRead because it performs much better than a loop (especially for large files).

To have multiple files open simultaneously, use FileOpen().

See Loop for information about Blocks, Break, Continue, and the A_Index variable (which exists in every type of loop).

To control how the file is decoded when no byte order mark is present, use FileEncoding.

## Related

FileEncoding, FileOpen() / File Object, FileRead, FileReadLine, FileAppend, Sort, Loop, Break, Continue, Blocks, FileSetAttrib, FileSetTime

## Examples

#1: Only those lines of the 1st file that contain the word FAMILY will be written to the 2nd file. Uncomment the first line to overwrite rather than append to any existing file.

    ;FileDelete, C:\Docs\Family Addresses.txt

    Loop, read, C:\Docs\Address List.txt, C:\Docs\Family Addresses.txt
    {
        if InStr(A_LoopReadLine, "family")
            FileAppend, %A_LoopReadLine%`n
    }

#2: Retrieve the last line from a text file.

    Loop, read, C:\Log File.txt
        last_line := A_LoopReadLine  ; When loop finishes, this will hold the last line.

#3: A working script that attempts to extract all FTP and HTTP URLs from a text or HTML file:

    FileSelectFile, SourceFile, 3,, Pick a text or HTML file to analyze.
    if (SourceFile = "")
        return  ; This will exit in this case.

    SplitPath, SourceFile,, SourceFilePath,, SourceFileNoExt
    DestFile := SourceFilePath "\" SourceFileNoExt " Extracted Links.txt"

    if FileExist(DestFile)
    {
        MsgBox, 4,, Overwrite the existing links file? Press No to append to it.`n`nFILE: %DestFile%
        IfMsgBox, Yes
            FileDelete, %DestFile%
    }

    LinkCount := 0
    Loop, read, %SourceFile%, %DestFile%
    {
        URLSearchString := A_LoopReadLine
        Gosub, URLSearch
    }
    MsgBox %LinkCount% links were found and written to "%DestFile%".
    return

    URLSearch:
    ; It's done this particular way because some URLs have other URLs embedded inside them:
    URLStart1 := InStr(URLSearchString, "https://")
    URLStart2 := InStr(URLSearchString, "http://")
    URLStart3 := InStr(URLSearchString, "ftp://")
    URLStart4 := InStr(URLSearchString, "www.")

    ; Find the left-most starting position:
    URLStart := URLStart1  ; Set starting default.
    Loop
    {
        ; It helps performance (at least in a script with many variables) to resolve
        ; "URLStart%A_Index%" only once:
        ArrayElement := URLStart%A_Index%
        if (ArrayElement = "")  ; End of the pseudo-array has been reached.
            break
        if (ArrayElement = 0)  ; This element is disqualified.
            continue
        if (URLStart = 0)
            URLStart := ArrayElement
        else ; URLStart has a valid position in it, so compare it with ArrayElement.
        {
            if (ArrayElement != 0)
                if (ArrayElement < URLStart)
                    URLStart := ArrayElement
        }
    }

    if (URLStart = 0)  ; No URLs exist in URLSearchString.
        return

    ; Otherwise, extract this URL:
    URL := SubStr(URLSearchString, URLStart)  ; Omit the beginning/irrelevant part.
    Loop, parse, URL, %A_Tab%%A_Space%<>  ; Find the first space, tab, or angle (if any).
    {
        URL := A_LoopField
        break  ; i.e. perform only one loop iteration to fetch the first "field".
    }
    ; If the above loop had zero iterations because there were no ending characters found,
    ; leave the contents of the URL var untouched.

    ; If the URL ends in a double quote, remove it.  For now, StringReplace is used, but
    ; note that it seems that double quotes can legitimately exist inside URLs, so this
    ; might damage them:
    StringReplace, URLCleansed, URL, ",, All
    FileAppend, %URLCleansed%`n
    LinkCount += 1

    ; See if there are any other URLs in this line:
    CharactersToOmit := StrLen(URL)
    CharactersToOmit += URLStart
    URLSearchString := SubStr(URLSearchString, CharactersToOmit)
    Gosub, URLSearch  ; Recursive call to self.
    return