Lua入门学习笔记

因为蓝牙耳机每次开机都会同时连接电脑和手机,而电脑响个提示音就会打断手机正在播放的视频。(感觉这个蓝牙耳机不太聪明的样子) 所以寻找了一下电脑锁屏关闭蓝牙的实现方法,被我找到了一个还挺好用的工具: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
1
2
3
4

但 macOS 用户,我更推荐用 homebrew 安装:

brew install lua 
1

我是使用 homebrew 来安装的。

运行环境安装完成后,可以尝试使用lua -v查看 lua 版本号来确认已成功安装 lua 环境。也可以运行一个简单的 helloworld:

-- 新建main.lua
print("Hello World!")
1
2

在 main.lua 所在的目录运行lua main.lua,如果成功输出 helloworld 则说明环境安装成功。

除了通过lua命令运行脚本文件以外,还可以通过lua -i来运行交互编程模式。 进入该模式后,每输入一行lua命令并回车都会运行该行命令。

# 基本语法

# 注释

-- 单行注释
--[[
  多
  行
  注
  释
]]--
1
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
1

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 字符串

Lua 中字符串可以使用以下三种方式来表示:

  1. 单引号间的一串字符
  2. 双引号间的一串字符
  3. [[ 和 ]] 间的一串字符
str1 = "test"
print("\"字符串 1 是\"",str1)  -- "字符串 1 是"   nil
str2 = 'foo'
print("字符串 2 是",str2)  -- 字符串 2 是     foo
str3 = [["bar"]]
print("字符串 3 是",str3)  -- 字符串 3 是     "bar"
1
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:

  1. 基础操作方法

    • string.upper(argument)

      将字符串全部转为大写字母

    • string.lower(argument)

      将字符串全部转为小写字母

    • string.reverse(arg)

      字符串反转

    • string.len(arg)

      计算字符串长度

    • string.rep(string, n)

      返回字符串 string 的 n 个拷贝

      string.rep('123abc', 5)  -- 123abc123abc123abc123abc123abc
      
      1
    • string.format(...)

      返回一个类似 printf 的格式化字符串,字符串格式化的语法下面会介绍

      string.format("the value is:%d", 4)  -- the value is:4
      
      1
  2. 字符与ASCII码互相转换

    • string.char(arg[, arg ...])

      将整型数字转成字符并拼接,接受 n 个参数

      string.char(97, 98, 99, 100)  -- abcd
      
      1
    • string.byte(arg[, int])

      将字符转化为整数值;仅能转换一个字符,默认第一个字符。int 用来指定 arg 字符串中的某个字符

      string.byte("abcd", 2)  -- 98
      
      1
  3. 查找、替换、迭代

    • 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
      5
    • string.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
      3
    • string.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
      3
    • string.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 接受一个字符串并按照给定的参数格式化该字符串

为进一步细化格式, 可以在 % 号后添加参数. 参数将以如下的顺序读入:

  1. 符号:一个 + 号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号
  2. 占位符:一个 0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格
  3. 对齐标识:在指定了字串宽度时, 默认为右对齐, 增加 - 号可以改为左对齐,左对齐会导致占位符 0 失效
  4. 宽度数值
  5. 小数位数 / 字串截断:在宽度数值后增加的小数部分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
1
2
3
4
5
6
7
8

# 字符串匹配模式

string.findstring.matchstring.gsubstring.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()
1
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
1
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
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 参考文档