# Loop (读取文件内容)

逐行读取文本文件的内容, 每次一行(比 FileReadLine 执行的更好).

    Loop, Read, InputFile , OutputFile

## 参数

Read

  此参数必须为单词 READ.

InputFile

  需要在循环中读取内容的文本文件的名称, 如果未指定绝对路径, 则假定在 %A_WorkingDir% 中. 支持 Windows 和 Unix 格式; 即文件的行结束符可以是回车和换行符(`r`n) 或只是换行符(`n).

OutputFile

  (可选的) 在循环期间保持打开的文件的名称, 如果未指定绝对路径, 则假定在 %A_WorkingDir% 中.

  在循环体内, 使用仅带一个参数(写入的文本) 的 FileAppend 命令来追加文本到此指定的文件. 以这种方式追加文本到文件比使用 2 个参数模式的 FileAppend 执行地更好, 因为每次操作时不需要关闭文件再重新打开. 如果需要, 记得在写入的文本后加上换行符(`n).

  如果没有写入内容到此文件, 那么它不会被打开. 当 Loop 的循环次数为零或其循环体中没有使用 FileAppend 命令时会发生这种情况.

  行结束(EOL) 转换: 在文件名前面加上星号可禁用 EOL 转换. 这使得每个换行符(`n) 作为单个换行符(LF) 而不是 Windows 标准的 CR+LF 写入. 例如:`*C:\My Unix File.txt`. 即使没有星号, 如果 Loop 中首次使用 FileAppend 时写入任何回车和换行符对(`r`n), 这时 EOL 转换也会自动禁止.

  标准输出(stdout): 指定 OutputFile 为星号(*) 可以把 FileAppend 写入的任何文本发送到标准输出(stdout). 这样的输出可以重定向到文件, 指向另一个 EXE 的管道或被高级文本编辑器捕获, 然而, 发送到标准输出的文本不会出现在运行它的命令提示符中. 这可以通过以下方法解决 1) [v1.1.33+] 使用 Ahk2Exe ConsoleApp 指令编译脚本, 或 2) 把脚本的输出通过管道传递给其他命令或程序. 请参阅 FileAppend 了解详细信息.

  逗号转义: 与其他大多数命令的最后一个参数不同, 在 OutputFile 中的逗号必须进行转义(`,).

## 备注

当您想对包含在文本文件中的内容逐行进行操作时, 文件读取循环很有用. 它比 FileReadLine 执行的更好, 因为: 1) 在整个操作过程中文件一直保持打开状态; 2) 不需要每次都重新扫描文件来查找请求的行内容.

内置变量 A_LoopReadLine 存在于任何文件读取循环中. 它包含了已去除行尾的回车换行符(`r`n) 的当前行内容. 如果一个内层文件读取循环包含在一个外层文件读取循环中, 则最内层循环的文件行将具有优先权.

最大可以读取包含 65,534 字符的长行. 如果某行超出这个长度, 此行的剩余字符将在下一次循环中读取.

经常在文件读取循环中使用 StrSplit(), StringSplit 或解析循环解析从 InputFile 读取的每行内容. 例如, 如果 InputFile 文件中的各行是由 tab 分隔的片段, 那么可以单独获取这些片段, 例如:

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

要加载整个文件内容到变量中, 使用 FileRead, 因为它比循环执行的更好(尤其对于大文件).

要同时打开多个文件, 请使用 FileOpen() 或参考此例使用 DllCall().

请参阅 Loop 了解关于区块, Break, Continue 和 A_Index 变量(其存在于各种类型的循环中) 的相关信息.

在不存在字节顺序标记时要控制如何解码文件, 使用 FileEncoding.

## 相关

FileEncoding, FileOpen() / 文件对象, FileRead, FileReadLine, FileAppend, Sort, Loop, Break, Continue, 区块, FileSetAttrib, FileSetTime

## 示例

只有首个文件中包含单词 FAMILY 的那些行才会写入第二个文件中. 取消注释首行可以覆盖而不追加到任何现有的文件.

    ;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
    }

获取文本文件的最后一行.

    Loop, read, C:\Log File.txt
        last_line := A_LoopReadLine  ; 当循环结束时, 这里会保持最后一行的内容.

尝试从文本或 HTML 文件中提取所有 FTP 和 HTTP URL 的有效脚本.

    FileSelectFile, SourceFile, 3,, Pick a text or HTML file to analyze.
    if (SourceFile = "")
        return  ; 此时将退出.

    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:
    ; 由于在一些 URL 中内嵌有其他的 URL, 所以用这种特殊的方法进行处理:
    URLStart1 := InStr(URLSearchString, "https://")
    URLStart2 := InStr(URLSearchString, "http://")
    URLStart3 := InStr(URLSearchString, "ftp://")
    URLStart4 := InStr(URLSearchString, "www.")

    ; 查找最左边的起始位置:
    URLStart = %URLStart1%  ; 设置默认的起始处.
    Loop
    {
        ; 仅解析 "URLStart%A_Index%" 一次有助于
        ; 改善性能 (尤其在含有许多变量的脚本中):
        ArrayElement := URLStart%A_Index%
        if (ArrayElement = "")  ; 到达伪数组的末尾.
            break
        if (ArrayElement = 0)  ; 此元素无效.
            continue
        if (URLStart = 0)
            URLStart := ArrayElement
        else ; URLStart 中含有有效位置, 所以把它与 ArrayElement 进行比较.
        {
            if (ArrayElement != 0)
                if (ArrayElement < URLStart)
                    URLStart := ArrayElement
        }
    }

    if (URLStart = 0)  ; 在 URLSearchString 中不存在网址.
        return

    ; 否则, 提取该网址:
    URL := SubStr(URLSearchString, URLStart)  ; 忽略开始/无效部分.
    Loop, parse, URL, %A_Tab%%A_Space%<>  ; 找到首个空格, tab 或尖括号(如果有).
    {
        URL := A_LoopField
        break  ; 即仅执行一次循环来获取首个 "片段".
    }
    ; 如果没有找到结束字符而在上面的循环中没有执行循环体,
    ; 那么保持网址变量的内容不变.

    ; 如果网址以双引号结束, 那么移除它. 暂时使用 StringReplace, 不过注意
    ; 双引号似乎可以合法存在于网址中, 所以这样做
    ; 可能破坏它们:
    StringReplace, URLCleansed, URL, ",, All
    FileAppend, %URLCleansed%`n
    LinkCount += 1

    ; 看看当前行中是否含有其他网址:
    CharactersToOmit := StrLen(URL)
    CharactersToOmit += URLStart
    URLSearchString := SubStr(URLSearchString, CharactersToOmit)
    Gosub, URLSearch  ; 到自身的递归调用.
    return

dftg