因为蓝牙耳机每次开机都会同时连接电脑和手机,而电脑响个提示音就会打断手机正在播放的视频。(感觉这个蓝牙耳机不太聪明的样子)
所以寻找了一下电脑锁屏关闭蓝牙的实现方法,被我找到了一个还挺好用的工具:hammerspoon (opens new window)。
hammerspoon 是一个强大的 OS X(现在应该叫 MacOS 了)自动化工具。
从本质上讲,赋予 hammerspoon 强大功能的是一组向用户公开特定系统功能的扩展,hammerspoon 只是操作系统和 lua 脚本引擎之间的桥梁。
所以,为了能更好的使用 hammerspoon(为了能看懂别人的配置),需要先熟悉一下 lua 这门语言。
# 环境安装
官网介绍的 linux 配置方法如下:
curl -R -O http://www.lua.org/ftp/lua-5.4.3.tar.gz
tar zxf lua-5.4.3.tar.gz
cd lua-5.4.3
make all test
2
3
4
但 macOS 用户,我更推荐用 homebrew 安装:
brew install lua
我是使用 homebrew 来安装的。
运行环境安装完成后,可以尝试使用lua -v
查看 lua 版本号来确认已成功安装 lua 环境。也可以运行一个简单的 helloworld:
-- 新建main.lua
print("Hello World!")
2
在 main.lua 所在的目录运行lua main.lua
,如果成功输出 helloworld 则说明环境安装成功。
除了通过lua命令运行脚本文件以外,还可以通过lua -i
来运行交互编程模式。
进入该模式后,每输入一行lua命令并回车都会运行该行命令。
# 基本语法
# 注释
-- 单行注释
--[[
多
行
注
释
]]--
2
3
4
5
6
7
# 标识符
lua 标识符以一个字母 A 到 Z、a 到 z、下划线 _ 开头后加上 0 或多个字母、下划线、数字 0 到 9 组成,区分大小写。 但标识符最好不要使用下划线加全大写字母的组合,因为这是 lua 内部保留字的格式,一般约定这种格式的标识符为 lua 内部的全局变量。
lua 的关键字如下表:
function | return | end | local | nil | true | false |
and | or | not | if | then | else | elseif |
break | for | in | while | until | repeat | do |
# 数据类型
lua 是动态类型语言,它有以下几种基本类型:
数据类型 | 描述 |
---|---|
nil | 该类型仅包含值 nil,表示空。还可以用来删除全局变量和 table。 |
boolean | 包含 true 和 false。在 if 判断时,仅有 false 和 nil 为假。 |
number | 数字,双精度(double)类型的实浮点数。 |
string | 字符串,由一对双引号或单引号来表示,可由两个方括号[[]] |
function | 由 C 或 Lua 编写的函数。 |
userdata | 表示任意存储在变量中的 C 数据结构。 |
thread | 表示执行的独立线路,用于执行协同程序。 |
table | lua中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是 {},用来创建一个空表。 |
通过 type 函数可以检测给定变量的类型:
type(print) -- function
lua 的隐式数据转换十分简单(javascript 应该反省一下)。 除了上面提到的,在判断操作中,仅有 false 和 nil 为假,其他均为真。 加法操作会尝试把两边的数据转换为数字,转换失败则抛出错误。
-- main.lua
print(a + 'error')
-- 报错信息:
--[[
lua: main.lua:2: attempt to add a 'nil' with a 'string'
stack traceback:
[C]: in metamethod 'add'
main.lua:1: in main chunk
[C]: in ?
]]--
-- 字符链接应该用 .. 操作符
print('b' .. 'error') -- berror
-- .. 操作符也会连接两个数字
print(123 .. 456) -- 123456
-- 还可以用 # 符号来计算字符串的长度
print(#('b' .. 'error')) -- 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 字符串
Lua 中字符串可以使用以下三种方式来表示:
- 单引号间的一串字符
- 双引号间的一串字符
- [[ 和 ]] 间的一串字符
str1 = "test"
print("\"字符串 1 是\"",str1) -- "字符串 1 是" nil
str2 = 'foo'
print("字符串 2 是",str2) -- 字符串 2 是 foo
str3 = [["bar"]]
print("字符串 3 是",str3) -- 字符串 3 是 "bar"
2
3
4
5
6
上面通过转义字符在 lua 字符串中插入了双引号。lua 中的所有转义字符如下表:
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表一个反斜线字符'' | 092 |
' | 代表一个单引号(撇号)字符 | 039 |
" | 代表一个双引号字符 | 034 |
\0 | 空字符(NULL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
除了上面在隐式转换中提到的..
操作符连接字符串,lua 还提供了很多操作字符串的方法。
下面就简单介绍一下各个 api:
基础操作方法
string.upper(argument)
将字符串全部转为大写字母
string.lower(argument)
将字符串全部转为小写字母
string.reverse(arg)
字符串反转
string.len(arg)
计算字符串长度
string.rep(string, n)
返回字符串 string 的 n 个拷贝
string.rep('123abc', 5) -- 123abc123abc123abc123abc123abc
1string.format(...)
返回一个类似 printf 的格式化字符串,字符串格式化的语法下面会介绍
string.format("the value is:%d", 4) -- the value is:4
1
字符与ASCII码互相转换
string.char(arg[, arg ...])
将整型数字转成字符并拼接,接受 n 个参数
string.char(97, 98, 99, 100) -- abcd
1string.byte(arg[, int])
将字符转化为整数值;仅能转换一个字符,默认第一个字符。int 用来指定 arg 字符串中的某个字符
string.byte("abcd", 2) -- 98
1
查找、替换、迭代
string.find(str, substr[ , init[ , plain]])
返回在字符串 substr 在 str 中的起始索引和结束索引,不存在则返回 nil;plain 默认为 false,传入 true 时关闭模式匹配
string.find('a1b2c3d4', 'b2') -- 3, 4 string.find('a1b2c3d4', 'b2', 3) -- 3, 4 string.find('a1b2c3d4', 'b2', 4) -- nil string.find('a1b2b2c3d4%a', '%a', 0, false) -- 1, 1 string.find('a1b2b2c3d4%a', '%a', 0, true) -- 11, 12
1
2
3
4
5string.match(str, pattern[, init])
只寻找源字串str中的第一个配对;参数init可选, 指定搜寻过程的起点, 默认为1;在成功配对时, 函数将返回配对表达式中的所有捕获结果;如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil
string.match('a1b2c3d4', '%a%d') -- 'a1' string.match('a1b2c3d4', '%a%d', 5) -- 'c3' string.match('a1b2c3d4', '%a%d', 9) -- nil
1
2
3string.gsub(mainString, findString, replaceString, num)
将字符串 mainString 的 字符串 findString 替换为字符串 replaceString num 为替换次数,如果忽略,默认为全部替换;返回结果除了替换后的字符串,还有替换次数
-- 参数不足时会报错,就不放出来了 string.gsub('a1a1a1a1', 'a1', 'e5') -- 'e5e5e5e5', 4 string.gsub('a1a1a1a1', 'a1', 'e5', 2) -- 'e5e5a1a1', 2
1
2
3string.gmatch(str, pattern)
返回一个迭代器函数 每次调用迭代器函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串 如果参数 pattern 描述的字符串没有找到,迭代函数返回nil
# 字符串格式化
string.format(...)
中第一个参数内可设置的、可被后续参数替换的转义码如下表:
转义码 | 说明 |
---|---|
%c | 接受一个数字, 并将其转化为 ASCII 码表中对应的字符 |
%d, %i | 接受一个数字并将其转化为有符号的整数格式 |
%o | 接受一个数字并将其转化为八进制数格式 |
%u | 接受一个数字并将其转化为无符号整数格式 |
%x | 接受一个数字并将其转化为十六进制数格式, 使用小写字母 |
%X | 接受一个数字并将其转化为十六进制数格式, 使用大写字母 |
%e | 接受一个数字并将其转化为科学记数法格式, 使用小写字母e |
%E | 接受一个数字并将其转化为科学记数法格式, 使用大写字母E |
%f | 接受一个数字并将其转化为浮点数格式 |
%g, %G | 接受一个数字并将其转化为%e (%E, 对应%G) 及 %f 中较短的一种格式 |
%q | 接受一个字符串并将其转化为可安全被Lua编译器读入的格式 |
%s | 接受一个字符串并按照给定的参数格式化该字符串 |
为进一步细化格式, 可以在 % 号后添加参数. 参数将以如下的顺序读入:
- 符号:一个 + 号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号
- 占位符:一个 0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格
- 对齐标识:在指定了字串宽度时, 默认为右对齐, 增加 - 号可以改为左对齐,左对齐会导致占位符 0 失效
- 宽度数值
- 小数位数 / 字串截断:在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留 n 位 若后接 s(字符串转义符, 如%5.3s)则设定该字符串只显示前 n 位
string1 = "Lua"
string2 = "Tutorial"
print(string.format("基本格式化 %012s %0-12s end",string1,string2)) -- 基本格式化 000000000Lua Tutorial end
date = 2; month = 1; year = 2014
print(string.format("日期格式化 %02d/%0-2d/%03d", date, month, year)) -- 日期格式化 02/1 /2014
print(string.format("%.4f",1/3)) -- 0.3333
2
3
4
5
6
7
8
# 字符串匹配模式
string.find
、string.match
、string.gsub
、string.gmatch
中的 pattern 参数可用的转义码如下表:
转义码 | 说明 |
---|---|
.(点) | 与任何字符配对 |
%a | 与任何字母配对 |
%c | 与任何控制符配对(例如\n) |
%d | 与任何数字配对 |
%l | 与任何小写字母配对 |
%p | 与任何标点(punctuation)配对 |
%s | 与空白字符配对 |
%u | 与任何大写字母配对 |
%w | 与任何字母/数字配对 |
%x | 与任何十六进制数配对 |
%z | 与任何代表0的字符配对 |
%x | 与字符x (非字母非数字)配对,主要用来处理表达式中有功能的字符(^$()%.[]*+-?)的配对问题,例如%%与%配对 |
[数个字符类] | 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对 |
[^数个字符类] | 与任何不包含在[]中的字符类配对. 例如[^%s]与任何非空白字符配对 |
当上述的字符类用大写书写时,表示与非此字符类的任何字符配对。例如,%S表示与任何非空白字符配对,%A表示与任何非字母的字符配对。
除了上述特殊字符,pattern 的构成还有以下规则:
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 '*',将匹配零或多个该类的字符。这个条目总是匹配尽可能长的串;
- 单个字符类跟一个 '+',将匹配一或更多个该类的字符。这个条目总是匹配尽可能长的串;
- 单个字符类跟一个 '-',将匹配零或更多个该类的字符。和
*
不同,这个条目总是匹配尽可能短的串; - 单个字符类跟一个 '?',将匹配零或一个该类的字符。只要有可能,它会匹配一个;
- 最前面加上符号 '^' 将锚定从字符串的开始处做匹配。出现在其它位置没有特殊含义,只表示自身;
- 最后面加上符号 '$' 将使匹配过程锚定到字符串的结尾。出现在其它位置没有特殊含义,只表示自身;
- %bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式;
- %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\0' 一样;
- %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串;
pattern 可以在内部用小括号括起一个子模式,这些子模式被称为捕获物。 当匹配成功时,由捕获物匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。 例如,对于模式 "(a*(.)%w(%s*))" ,字符串中匹配到 "a*(.)%w(%s*)" 的部分保存在第一个捕获物中,因此是编号1。
# 变量
lua 中的变量有三种类型:全局变量、局部变量、table 中的域。
除非使用local
显示声明为局部变量,lua 中的变量全部都是全局变量,哪怕是在语句块或者函数里。
局部变量的作用域为从声明位置开始到所在语句块的结束,变量的默认值均为nil
。
lua 可以对多个变量同时赋值:
a, b, c = 1, 3, 5 -- a = 1, b = 3, c = 5
a, b, c = 1, 3 -- a = 1, b = 3, c = nil
a, b, c = 1, 3, a -- a = 1, b = 3, c = nil
a, b = 1, 2
a, b = b, a -- a = 2, b = 1
-- 多值赋值经常用来捕获函数的返回值
a, b = f()
2
3
4
5
6
7
8
9
10
11
# 循环
lua 中共有三种循环:while、for、repeat until
num = 0
while num < 10 do
num = num + 1 -- lua中没有 ++ 或者 += 操作符
end
sum = 0
for i = 1, 10 do
sum = sum + i
end
sum_1 = 0
for j = 10, 1, -1 do
sum_1 = sum_1 + j
end
count = 5
repeat
print('the way of the future')
count = count - 1
until count == 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 条件语句
num = 0
if type(num) ~= 'number' then
print('wrong type')
elseif num > 40 then -- ~= is not equals.
print('over 40') -- Defaults to stdout.
else
print('not over 40')
end
if not num then
print('num was falsy') -- 0 不是 falsy,不会暑促
end
2
3
4
5
6
7
8
9
10
11
12
# 运算符
lua 提供的运算符都是比较常见的,这里仅简单列举一下。
- 算数运算符
操作符 | 描述 |
---|---|
+ | 加法 |
- | 减法 / 负号 |
* | 乘法 |
/ | 除法 |
% | 取余 |
^ | 乘幂 |
- 关系运算符
操作符 | 描述 |
---|---|
== | 等于 |
~= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
- 逻辑运算符
操作符 | 描述 |
---|---|
and | 逻辑操作符与 |
or | 逻辑操作符或 |
not | 逻辑操作符非 |
- 其他运算符
操作符 | 描述 |
---|---|
.. | 连接两个字符串 |
# | 一元运算符,返回字符串或表的长度 |
# 函数
lua 语言定义函数的语法格式如下:
-- 伪代码
optional_function_scope function function_name(argument1, argument2..., argumentn)
function_body
return result_params_comma_separated
end
-- optional_function_scope:可选参数,用于设置函数是全局函数还是局部函数。
-- 未设置该参数默认为全局函数,设置函数为局部函数需要使用关键字 local
-- function_name:函数名称
-- argument:函数参数,多个参数以逗号隔开,也可以不带参数
-- function_body:函数体,代码语句块
-- result_params_comma_separated:返回值,可以返回多个值,每个值以逗号隔开
-- 回溯计算斐波那契数列
function fib(n)
if n < 2 then
return 1
end
return fib(n - 2) + fib(n - 1)
end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20