脚本

相关话题:

目录

简介

每个脚本都是纯文本文件, 其中包含了可由程序(AutoHotkey.exe) 执行的文本行. 脚本中还可以包含热键热字串或者甚至完全由它们组成. 不过, 在不包含热键和热字串时, 脚本会在启动后从上往下按顺序执行其中的命令.

程序会把脚本逐行加载到内存中, 每行最多可以包含 16,383 个字符. 在加载过程中, 脚本会被优化和检查. 将列出所有的语法错误, 更正它们后脚本才能运行.

脚本顶部(自动执行段)

脚本加载完成后, 它会从顶行开始执行, 直到遇到 Return, Exit, 热键/热字串标签或脚本的底部(无论最先遇到哪个). 脚本的这个顶端部分被称为 自动执行段.

注意: 虽然脚本的 第一个 热键/热字串标签与 return(返回) 的效果相同, 但其他热键和标签却没有.

如果脚本不是持续运行的且不含有热键, 热字串, OnMessage()GUI, 那么它会在自动执行段结束后终止. 否则, 它会以空闲状态继续运行, 从而对例如热键, 热字串, GUI 事件, 自定义菜单项计时器这些事件进行响应.

每个由热键, 热字串, 菜单项, GUI 事件计时器启动的线程都以在自动执行段设置的下列属性值作为默认值开始. 如果没有设置, 则使用标准的默认值(与下面每个页面中注明的一样): AutoTrim, CoordMode, Critical, DetectHiddenText, DetectHiddenWindows, FileEncoding, ListLines, SendLevel, SendMode, SetBatchLines, SetControlDelay, SetDefaultMouseSpeed, SetFormat, SetKeyDelay, SetMouseDelay, SetRegView, SetStoreCapsLockMode, SetTitleMatchMode, SetWinDelay, StringCaseSenseThread.

如果自动执行段执行了很长时间才结束(或永不结束), 上面这些设置的默认值将在 100 毫秒后自动生效. 当自动执行段最终结束(如果可能) 时, 在自动执行段末尾生效的那些设置才更新为默认值. 因此, 通常最好在包含热键, 热字串, 计时器自定义菜单项的脚本顶部设置自己想要的默认值. 还要注意, 每个线程会保存它自己的上述设置的集合. 在一个线程中对这些设置的改变不会影响其他线程.

把过长的行分割成一系列短行

可以把过长的行分割成一系列较短的行来提高可读性和可维护性. 这样不会降低脚本的执行速度, 因为在脚本启动时这些短行会在内存中合并起来.

方法 #1: 以 "and", "or", ||, &&, 逗号或句点开始的行会自动合并到其前一行(在 v1.0.46+, 除了 ++ 和 -- 外其他所有的表达式运算符开头的行也会如此). 在下面的例子中, 第二行会自动附加到首行, 因为它以逗号开始:

FileAppend, This is the text to append.`n   ; 这里可以使用注释.
    , %A_ProgramFiles%\SomeApplication\LogFile.txt  ; 注释.

同样地, 下列几行也会合并成单行, 因为最后两行以 "and" 或 "or" 开始:

if (Color = "Red" or Color = "Green"  or Color = "Blue"   ; 注释.
    or Color = "Black" or Color = "Gray" or Color = "White")   ; 注释.
    and ProductIsAvailableInColor(Product, Color)   ; 注释.

三元运算符也是个不错的选择:

ProductIsAvailable := (Color = "Red")
    ? false  ; 我们没有任何红色产品, 所以不用那么麻烦去调用函数.
    : ProductIsAvailableInColor(Product, Color)

尽管在上面的例子中缩进不是必须的, 但它可以显示出哪些行属于上一行从而提高代码清晰度. 并且, 可以不必在单词 "AND" 和 "OR" 开始的行加上额外的空格; 程序会自动处理这些. 最后, 可以在上面例子中任意行的末尾或行与行之间添加空行或注释.

方法 #2: 这种方法用于合并大量的行或不适合方法 #1 处理的行. 此方法对自动替换热字串特别有用, 但它也可以用于命令或表达式中. 例如:

; 示例 #1:
Var =
(
Line 1 of the text.
Line 2 of the text. By default, a linefeed (`n) is present between lines.
)

; 示例 #2:
FileAppend,  ; 此时逗号是不能缺少的.
(
A line of text.
By default, the hard carriage return (Enter) between the previous line and this one will be written to the file as a linefeed (`n).
    By default, the tab to the left of this line will also be written to the file (the same is true for spaces).
By default, variable references such as %Var% are resolved to the variable's contents.
), C:\My File.txt

在上面的例子中, 几行代码被一对圆括号包围起来. 这被称为 延续片段. 注意在底行的闭括号后面跟着 FileAppend 的最后一个参数. 这种做法是可选的; 这样做是为了确保逗号被视为参数分隔符而不是原义的逗号.

通过在延续片段的括号右侧包含一个或多个下列选项, 可以覆盖它的默认特性. 如果含有多个选项, 那么选项之间使用空格分开. 例如: ( LTrim Join| %.

Join: 指定行与行之间连接的方式. 如果省略此选项, 那么除最后一行外的其他行后面都会跟一个换行符(`n). 如果指定单词 Join 自身, 则行与行之间直接连接而不添加任何字符. 或者在单词 Join 后可以紧跟着多达 15 个字符. 例如, Join`s 会在除最后一行外的每行末尾插入一个空格("`s"表示原义的空格, 这是一个只能被 Join 识别的特殊转义序列). 另一个例子是 Join`r`n, 它会在行与行之间插入 CR+LF. 同样地, Join| 会在行之间插入管道符. 要让延续片段的最后一行也以连接字符串结尾, 需要在它的闭括号上一行添加一个空行.

已知限制: 如果单词 Join 后面紧跟一个冒号, 那么冒号不能是这一行的最后一个. 因为 (Join: 将被当做一个名为 Join 的标签对待, 所以不支持 (LTrim Join: 这种写法, 而 (Join: C 这种写法没有问题.

LTrim: 删除每行开头的空格和 tab. 主要用来允许延续片段使用缩进. 此外, 通过在一行中指定 #LTrim 自身可以为多个延续片段打开此选项. #LTrim 跟位置有关: 会影响它下面的所有延续片段. 使用 #LTrim Off 可以关闭此设置.

RTrim0(RTrim 后跟着一个零): 关闭自动删除每行末尾的空格和 tab 的设置.

Comments(或 CommentComC) [v1.0.45.03+]: 允许在延续片段中使用分号注释(但不支持 /*..*/). 这样的注释(以及它们左边的空格和 tab) 会在连接时完全被忽略而不是当成原义文本处理. 每个注释可以放在一行的右侧或单独一行.

%(百分号): 把百分号视为原义字符而不是变量引用. 这样就不需要把每个百分号转义成原义字符. 在百分号已经为原义的地方不需要使用此选项, 例如, 自动替换热字串.

,(逗号): 把逗号作为分隔符而不是原义逗号. 这个非常少用的选项只有在命令的参数之间才需要, 因为在函数调用中逗号的类型没有影响. 并且, 此选项只会转换那些真正的分隔参数的逗号. 换句话说, 一旦到了命令的最后一个参数(或者命令没有参数), 那么会忽略此选项而把后续的逗号当成原义逗号.

`(重音符): 把每个反引号视为原义字符而不是转义符. 此选项同时也避免了需要分别对逗号和百分号进行明确地转义. 此外, 它还阻止对任何特定的转义序列例如 `r 和 `t 进行转义.

) [v1.1.01+]: 如果一个右括号出现在延续代码的参数中(除了作为 Join 的选项参数), 那么这一行将重新解释为一个表达式, 而不是作为一段延续代码的开始. 这将避免类似 (x.y)[z]() 的表达式需要对左括号进行转义.

备注

当没有指定重音符(`) 选项时, 支持在延续片段中使用转义序列, 例如 `n(换行) 和 `t(tab).

没有使用注释选项时, 不支持在延续片段中使用分号和 /*..*/ 添加注释, 因为它们会被视为原义文本. 不过, 可以在片段的底行和顶行添加注释. 例如:

FileAppend,   ; 注释.
; 注释.
( LTrim Join    ; 注释.
     ; This is not a comment; it is literal. Include the word Comments in the line above to make it a comment.
), C:\File.txt   ; 注释.

由上面可知, 延续片段中的分号不需要进行转义.

使用延续片段无法生成总长度超过 16,383 字符的行(如果尝试这么做, 那么程序在启动时会弹出警告). 解决此问题的一种方法是把一系列内容连接到变量中. 例如:

Var =
(
...
)
Var = %Var%`n  ; 通过另一个延续片段添加更多文本到此变量中.
(
...
)
FileAppend, %Var%, C:\My File.txt

因为闭括号表示延续片段的结束, 所以要让某一行以原义的闭括号开头, 需要在其前面加上重音符/反引号: `).

一个延续片段后面可以紧跟着包含另一个延续片段的开括号的一行. 这样使得上面提到的选项可以在创建单行的过程中进行改变.

不支持通过 #Include 的方法把延续片段各部分连接起来.

把脚本转换成 EXE(Ahk2Exe)

程序中已包含了脚本编译器(感谢 fincs 提供, 以及 TAC109 的补充).

脚本编译完成后, 就成了一个独立的可执行文件; 也就是说, 可以在没有安装 AutoHotkey 的机器上运行. 编译过程中会创建一个包含下列文件的可执行文件: AutoHotkey 解释器, 脚本, 脚本包含的任何文件以及通过 FileInstall 命令内嵌的任何文件. [v1.1.33+]: 可以使用编译器指令包含其他文件.

使用 Ahk2Exe 有下列几种方式:

注意:

编译器的源码和新版本可以在 GitHub 找到.

脚本编译器指令

[v1.1.33+]: 脚本编译器指令允许用户指定如何编译脚本的细节. 其中的一些功能是:

更多细节, 请参阅脚本编译器指令.

压缩编译脚本

Ahk2Exe 可以选择使用 MPRESS 或 [v1.1.33+] UPX 免费软件来压缩编译后的脚本. 如果 MPRESS.exe 和/或 UPX.exe 已被复制到安装 AutoHotkey 的 "Compiler" 子文件夹中, 则可以通过 /compress 参数或 GUI 设置来压缩 .exe 文件.

MPRESS 官网(介绍与下载): http://www.matcode.com/mpress.htm
MPRESS 镜像: https://www.autohotkey.com/mpress/

UPX 官网(介绍与下载): https://upx.github.io/

注意: 压缩编译脚本可以保护脚本不被诸如记事本或 PE 编辑器这类的工具随意查看, 但并不能保护脚本源码不被专用的提取工具提取.

向脚本传递命令行参数

脚本支持命令行参数. 格式为:

AutoHotkey.exe [Switches] [Script Filename] [Script Parameters]

对于已编译脚本, 格式为:

CompiledScript.exe [Switches] [Script Parameters]

Switches: 零个或多个下列开关:

开关释义是否支持编译?
/f 或 /force 无条件强制启动, 忽略任何警告对话框. 和 #SingleInstance Off 的效果相同.
/r 或 /restart 脚本将会执行 Reload 命令.
/ErrorStdOut

/ErrorStdOut=Encoding

把阻止脚本运行的语法错误发送到标准错误流(stderr) 而不显示在对话框中. 请参阅 #ErrorStdOut 了解详情. 这可以联合 /iLib 在不运行脚本的情况下验证脚本.

[v1.1.33+]: 可以选择指定编码. 例如, /ErrorStdOut=UTF-8 在将消息写入 stderr 之前将其编码为 UTF-8.

/Debug [AHK_L 11+]: 连接到调试客户端. 想了解更多细节, 请参阅交互调试.
/CPn

[AHK_L 51+]: 覆盖用于读取脚本文件的默认代码页. 想了解更多信息, 请参阅脚本文件代码页.

[v1.1.33+]: 如果在安装程序中启用了 "Default to UTF-8" 选项, 那么 ".ahk" 文件类型就会在包括 /CP65001 在内的命令行中注册. 这将导致所有通过 shell(Explorer(资源管理器)) 启动的脚本在没有 UTF-16 字节顺序标记的情况下默认为 UTF-8. 通过直接运行 AutoHotkey.exe 启动的脚本仍然默认为 CP0, 因为没有 /CP65001 开关.

/iLib "OutFile"

[v1.0.47+]: AutoHotkey 加载但不运行脚本. 通过库机制自动 include 每个脚本, 两行代码会写到 OutFile 指定的文件里面. 这些行以以下格式编写, 其中 LibDir 是 Lib 文件夹的完整路径, LibFile 是库的文件名:

#Include LibDir\
#IncludeAgain LibDir\LibFile.ahk

如果输出文件存在会被覆盖. OutFile 可以是 * 从而将输出写到 stdout 中.

如果脚本语法错误, 那么输出文件可能为空. 程序退出代码可以检测这种情况; 如果这里有语法错误, 退出代码就是 2. /ErrorStdOut 开关可用于抑制或捕获错误消息.

Script Filename: 如果不含 Script Parameters, 那么此参数可以省略. 省略时, 它会运行(或提示您创建) 下列默认位置的其中一个文件:

AutoHotkey.ahk 的文件名取决于 Autohotkey 的文件名. 例如重命名 AutoHotkey.exe 为 MyScript.exe, 它会尝试打开 MyScript.ahk. 如果你运行 AutoHotkeyU32.exe 没有参数则会运行 AutoHotkeyU32.ahk.

注意: 在早期版本 revision 51, 程序会在工作目录寻找 AutoHotkey.ini 或者我的文档下的 AutoHotkey.ahk.

[v1.1.17+]: 星号(*) 加在文件名上会打开标准输入通道(stdin) 的脚本. 例如 ExecScript().

Script Parameters: 你想要传递给脚本的参数用空格分隔. 单个参数如果包含空格需要在两端加上引号("). 如果参数本身包含引号(") 则需要转义(\"). 因此, 加引号参数(例如"C:\My Documents\") 末尾的斜杠(\) 都被当作原义的引号(就是说, 脚本将接收到字符串 C:\My Documents"). 要移除引号, 使用StringReplace, 1, 1, ",, All.

[v1.1.27+]: 传入参数, 如果存在, 作为数组存储在内置变量 A_Args 中, 可以使用数组语法来访问. A_Args[1] 包含第一个参数. 下面的示例在传递给它的参数太少时退出脚本:

if A_Args.Length() < 3
{
    MsgBox % "脚本需要至少 3 个参数, 但实际只接收到" A_Args.Length() "个."
    ExitApp
}

如果传递给脚本的参数数目不确定(可能是由于用户将一组文件拖放到脚本中), 则可以使用以下示例逐个提取它们:

for n, param in A_Args  ; 对每个参数进行循环:
{
    MsgBox Parameter number %n% is %param%.
}

如果参数是文件名, 则可以使用以下示例将它们转换为大小写校正的长名称(与文件系统中存储的一致), 包括完整/绝对路径:

for n, GivenPath in A_Args  ; 对每个参数(或拖放到脚本上的文件) 进行循环:
{
    Loop Files, %GivenPath%, FD  ; 包含文件和目录.
        LongPath := A_LoopFileFullPath
    MsgBox The case-corrected long path name of file`n%GivenPath%`nis:`n%LongPath%
}

已知限制: 如果 NTFS 文件系统中关闭了 8.3(短) 文件名, 那么拖拽文件到 .ahk 脚本上可能无法正常工作. 一种解决方法是编译脚本, 然后拖拉文件到生成的 EXE 文件上.

传统方法: 命令行参数也存储在变量 %1%, %2% 等中, 如 [v1.1.27] 之前的版本. 另外, %0% 包含传递参数的数量(如果没有则为 0). 但是, 这些变量不能在表达式中直接引用, 因为它们会被视为数字而不是变量. 下面的示例在传递给它的参数太少时退出脚本:

if 0 < 3  ; 非表达式 if-语句的左边始终是变量的名称.
{
    MsgBox 脚本需要至少 3 个参数, 但实际只接收到 %0% 个.
    ExitApp
}

如果传递给脚本的参数数目不确定(可能由于用户拖放一组文件到脚本上), 可以使用下面的示例逐个提取这些参数:

Loop, %0%  ; 对每个参数进行循环:
{
    param := %A_Index%  ; 取得名称为 A_Index 的值的变量中的内容.
    MsgBox, 4,, Parameter number %A_Index% is %param%.  Continue?
    IfMsgBox, No
        break
}

如果这些参数是文件名, 那么可以使用下列的例子把它们转换到大小写正确的长名称(与文件系统中存储的一致), 其中包含完整/绝对的路径.

Loop %0%  ; 对每个参数 (或拖放到脚本上的文件) 进行循环:
{
    GivenPath := %A_Index%  ; 取得名称为 A_Index 变量中的内容.
    Loop %GivenPath%, 1
        LongPath := A_LoopFileLongPath
    MsgBox The case-corrected long path name of file`n%GivenPath%`nis:`n%LongPath%
}

脚本文件代码页 [AHK_L 51+]

为了使非 ASCII 字符能够正确地从文件中读取, 文件保存时使用的编码(通常由文本编辑器) 必须与 AutoHotkey 读取文件时使用的编码一致. 如果不匹配, 字符将被错误地解码. AutoHotkey 使用以下规则来决定使用哪种编码:

注意这仅适用于 AutoHotkey 加载脚本的时候, 而不包括脚本自身的文件 I/O. FileEncoding 决定了脚本读取或写入文件时使用的默认编码, 然而 IniReadIniWrite 总是使用 UTF-16 或 ANSI.

由于所有的文本都被转换(必要时) 为原生的字符串格式, 所以无效的或不存在于原生代码页中的字符会被替换为占位符: ANSI '?' 或 Unicode '�'. 在 Unicode 版本中, 这种情况只可能在脚本文件编码错误或用于保存和读取脚本的代码页不一致时发生.

可以使用 RegWrite 为资源管理器中运行的脚本设置默认代码页(例如双击脚本文件时):

; 取消对下面适当的行的注释或让它们都保留注释
;   以重新设置为当前版本的默认代码页.  需要时进行修改:
; codepage := 0        ; 系统默认的 ANSI 代码页
; codepage := 65001    ; UTF-8
; codepage := 1200     ; UTF-16
; codepage := 1252     ; ANSI Latin 1; 西欧 (Windows)
if (codepage != "")
    codepage := " /CP" . codepage
cmd="%A_AhkPath%"%codepage% "`%1" `%*
key=AutoHotkeyScript\Shell\Open\Command
if A_IsAdmin    ; 为所有用户进行设置.
    RegWrite, REG_SZ, HKCR, %key%,, %cmd%
else            ; 仅为当前用户进行设置.
    RegWrite, REG_SZ, HKCU, Software\Classes\%key%,, %cmd%

这里假定已经安装了 AutoHotkey. 如果没有, 则结果可能不理想.

调试脚本

ListVarsPause 等命令可以帮助您调试脚本. 例如, 把下面这两行临时插入精心选择的位置时, 可以在脚本中创建 "断点":

ListVars
Pause

当脚本执行到这两行时, 会显示所有变量当前包含的内容供你检查. 当你准备恢复时, 可以通过 File 或托盘菜单取消暂停. 然后脚本会继续执行, 直到遇到下一个"断点"(如果有).

通常最好把这些 "断点" 插入到活动窗口对当前脚本没有影响的位置, 例如 WinActivate 命令的前一行. 这样当您取消暂停时脚本才可以正确恢复操作.

下列命令也可以用于调试: ListLines, KeyHistoryOutputDebug.

一些常见错误, 例如拼写错误或忘记 "global" 声明时, 可以使用启用警告检测到.

交互调试 [AHK_L 11+]

通过受支持的 DBGp 客户端可以进行交互调试. 一般支持下列操作:

注意在已编译脚本中没有提供此功能.

要启用交互调试, 首先要运行受支持的调试器客户端, 然后使用命令行开关 /Debug 运行脚本.

AutoHotkey.exe /Debug=SERVER:PORT ...

SERVERPORT 可以省略. 例如, 下面的方式是等同的:

AutoHotkey /Debug "myscript.ahk"
AutoHotkey /Debug=localhost:9000 "myscript.ahk"

[AHK_L 59+]: 要向已经在运行的脚本附加调试器, 请向脚本发送消息, 如下所示:

ScriptPath := "" ; 设置此变量为脚本的完整路径
DetectHiddenWindows On
if WinExist(ScriptPath " ahk_class AutoHotkey")
    ; 可选参数:
    ;   wParam  = 调试器客户端的 IPv4 地址, 相当于 32 位整数.
    ;   lParam  = 调试器客户端的监听端口.
    PostMessage DllCall("RegisterWindowMessage", "str", "AHK_ATTACH_DEBUGGER")

当调试器连接后, 通过发送 "detach" DBGp 命令可以在不终止脚本的情况下分离调试器.

脚本展示

请参阅此页面了解一些有用的脚本.