Lua 语法基础 | Nmap 脚本

2020-08-20 14:52:48 浏览数 (1)

这篇文章是我去年学习Lua 语言时候记下的笔记 Markdown 版本的可能被我误删了,只剩下了文字版,markdown版当时保存了一个 PDF,可以使用下面的链接下载(更美观) https://www.my-synology.cn:37443/sharing/3I8q4i1Xd 一共有 56 页,同时推荐给大家一本书 《Lua 程序设计第四版》


1. Hello World

1.1 如何执行一段Lua代码

1)交互模式下直接输入要执行的代码

2)lua demo.lua

3)使用dofile函数进行执行,例如 dofile("demo.lua")

1.2 一些词法规范

标识符(或名称)规范:任意字母、数字、下划线组成,不能以数字开头

一般

下划线 大写字母 被用作特殊用途,尽量咱们就不要用

下划线 小写字母 被用作哑变量(Dummy variable)

还有一些保留字,不能被设置为标识符

and break do else elseif end false goto for function if in local nil not or repeat return then true until while

Lua是大小写敏感的,比如 And 和 AND是两个不同的标识符

1.3 注释符

lua 中使用 -- 来表示单行注释

--[[多行注释]]

多行注释小技巧

在注释多行代码的时候,可以使用以下方式进行

--[[

print("codes")

--]]

这样的话,如果我们需要重新释放这段代码,只需要在第一行多加一个 - ,变成如下:

---[[

print("codes")

--]]

1.4 分隔符

在Lua语言中,连续语句之间的分隔符并不是必需的。如果有需要的话可以使用分号来进行分隔,换行其实是没啥用的

下面四个都是合法且等价的

---------------

a = 1

b = a * 2

---------------

a = 1;

b = a * 2;

---------------

a = 1; b = a * 2

---------------

a = 1 b = a * 2 -- 虽然可读性不佳,但是却是正确的

---------------

1.5 全局变量

Lua 中全局变量无须声明即可使用,使用未经初始化的全局变量也不会导致错误。当使用未经初始化的全局变量时,得到的结果是 nil

---------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> print(b)

nil

----------------

Lua 对于全局变量的回收可以直接将该全局变量赋值 nil

1.6 类型和值

Lua 语言一共有 8 种基本类型:

nil

boolean

number

string

userdata

function

thread

table

使用type函数可以直接获取一个值对应的类型名称

type(nil) --> nil

type(true) --> boolean

type(10.4 * 3) --> number

type("Hello World") --> string

type(io.stdin) --> userdata

type(print) --> function

type(type) --> thread

type({}) --> table

type(type(x)) --> string

关于 userdata

这种类型可以把任意C语言数据保存在Lua语言变量中,这个类型被用来被用来表示由应用或C语言编写的库所创建的新类型

,比如标准I/O库使用用户数据来表示打开的文件

关于 boolean ,false 和 nil 以外的所有其他值都视为真。没错。你没有看错。在条件检测中 Lua 语言把零和空字符串也都视为真

and or not 逻辑运算符的结果

【 and 】

> a and b

如果 a 为 false,则返回a;否则返回b;如果都为 nil,那么返回 nil

> 4 and 5

5

> nil and 5

nil

> nil and nil

nil

> false and nil

false

>

【 or 】

a or b

如果a为true,则返回a,否则返回b

> 4 or 6

4

> nil or 6

6

> false or 6

6

> false or nil

nil

>

其实就是遵循最短求值原则,即只在必要时才对第二个数进行求值

所以 Lua 中形如 x = x or v 这种非常常用

等价于 if not x then x = v end

1.7 独立解释器

说白了就是用来执行lua代码的程序

lua [options] [script [args]]

这里要说一下

1.7.1 -e 参数允许我们用来直接在命令行一句话执行代码

--------------------------------------

~ ❯❯❯ lua -e 'print("aaa")'

aaa

~ ❯❯❯

----------------------------------------

在 POSIX 系统(可移植操作系统接口)下需要使用双引号,以防止shell错误解析括号

1.7.2 -l 参数

这个参数用于加载库文件

1.7.3 -i 参数

这个参数用于在执行完其他命令行参数后进入交互模式

----------------------------------------

~ ❯❯❯ lua -i -e "x = 10; print(x)"

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

10

>

-----------------------------------------

默认情况下交互模式下会输出结果,如果不想输出,可以在表达式后面加一个分号,分号使得最后一行在语法上变成了无效表达式,但可以被当作有效的命令执行

-----------------------------------------

> io.flush()

true

> io.flush();

------------------------------------------

Lua 解释器在处理参数前,会查找两个环境变量,一个是 LUA_INIT_5_3 ;另一个是LUA_INIT 。先查找 LUA_INIT_5_3,如果找不到再查找 LUA_INIT,如果其中任意一个存在,则会审查其中的内容,如果是以 @filename 开头,那么解释器就会运行对应的文件了;如果环境变量存在,但是内容不是以 @filename 开头的,那么解释器会认为其中包含 Lua 代码,并且会对其中的文件进行解释执行

这样的话我们可以通过这两个环境变量完整地配置Lua,比如我们可以预先加载程序包、修改路径、定义自定函数、对函数进行重命名或删除函数等

我们可以通过预先定义的全局变量arg 来获取解释器传入的参数。例如,执行一下命令时

lua demo.lua a b c

全局变量 arg 中 arg[0] 永远是指 脚本名称

所以以上命令对应的arg 中的位置为

arg[-1] = lua

arg[0] = demo.lua

arg[1] = a

arg[2] = b

arg[3] = c

2.数值

2.1 数值常量

Lua 5.3 版本开始,数值格式分为两种选择,一种为 64为 interger ,另一种为 float 双精度浮点类型

整数型和浮点型数值都是 number ,要区分整数值和浮点型时,可以使用 math.type(a) 进行确认

-----------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> math.type(5)

integer

> math.type(5.3)

float

>

------------------------

整数和浮点数可以相互转化,具有相同算数值的整数型和浮点型值在 Lua 语言中是相等的

------------------------

> 1 == 1.0

true

> -3 == -3.0

true

> -3 == - 3.1

false

> 0.2e3 == 200 (e3表示十的三次方)

true

------------------------

对于十六进制数的支持

Lua 支持 0x 开头的十六进制数,同时,与其他语言不同的是:Lua 语言还支持十六进制的浮点数,这种十六进制的浮点数由小数部分和以 p 或 P 开头的指数部分组成。例如:

------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> 0xff

255

> 0x1A3

419

> 0x0.2

0.125

> 0x1p-1

0.5

> 0xa.bp2

42.75

>

还可以使用 string.format 函数对 %a 参数进行格式化输出,这种输出可以保留所有浮点数的精度

----------------------------------------------------

> string.format("this is a number:%a", 419)

this is a number:0x1.a3p 8

>

----------------------------------------------------

2.2 算术运算

除了基本的加、减、乘、除,Lua 还支持 floor 除法,这个除法的结果就是负向无穷取整

----------------------------------------------------

> 3//2

1

> 3.0//2

1.0

> 6//2

3

> 6.0//2.0

3.0

> -9//2

-5

> 1.5//0.5

3.0

>

------------------------------------------------------

a % b == a - ((a // b) * b)

圆周率pi math.pi

2.3 关系运算

Lua 语言提供了下列关系运算

< > <= >= == ~=

运算的结果都是 Boolean 类型

~= 是不等于

2.4 数学库

Lua 语言的标准数学库是 math。其中包括三角函数 (sin、cos、tan、asin等)、指数函数、取整函数、最大和最小函数max 和 min、用于生成伪随机数的伪随机数函数(random)以及常量pi和huge(最大数值,在大多数平台上代表 inf)

2.4.1 随机数发生器

函数 math.randon 用于生成伪随机数,共有三种调用方式

1) 直接调用

--------------------------------------------

> math.random()

0.84018771676347

> math.random()

0.39438292663544

> math.random()

0.78309922339395

> math.random()

0.79844003310427

>

----------------------------------------------

2) 使用random(x) 这种方式选择一个 1-x之间的一个整数

-----------------------------------------------

> math.random(5)

5

> math.random(5)

1

> math.random(5)

2

> math.random(5)

4

> math.random(5)

2

>

-----------------------------------------------

3) 使用 random(3,7),选择 3-7 中任意整数

-----------------------------------------------

> math.random(3,7)

5

> math.random(3,7)

5

> math.random(3,7)

6

> math.random(3,7)

4

> math.random(3,7)

5

> math.random(3,7)

7

>

-------------------------------------------------

2.4.2 取整函数

floor、ceil、modf

--------------------

floor 向负无穷取整

ceil 向正无穷取整

modf 向零取整

--------------------

> math.floor(3.3)

3

> math.ceil(3.3)

4

> math.modf(3.3)

3 0.3

>

--------------------

2.5 表示范围

标准lua 使用64个bit位来存储整数值, 2的63次方减一

3. 字符串

Lua 使用 8 个bit 来进行存储。Lua 可以存储任何编码的字符串

长度操作符 # 可以用来获取字符串长度

---------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> a = "hhhhhh"

> #a

6

>

----------------------

.. 两个点用来进行字符串拼接,如果拼接的不是字符串而是数值,那么lua会将数值转化为字符串

3.1 字符串常量

单引号双引号都可以用来声明字符串常量

a = "Hello world"

b = 'Hell old'

------------------------

> a = "this is 'danyinhao'"

> a

this is 'danyinhao'

> b = 'this "aaa"'

> b

this "aaa"

>

--------------------------

LUA 语言中的字符串支持,字符串支持一些C语言的转义字符

a

b

f

n 换行

r 回车

t

v 垂直制表符

\

"

'

LUA中还可以童年各国转义序列 ddd和xhh 的方式来声明字符串

10 代表换行符,类似的表达还有 char(10)

x4f

在一个使用ascii编码的系统中, "AL0n123"" 和 'x41L0104923' 实际上是一样的

x41 是十六进制的65,在ascii中是 A

10 是换行符

49 是 1 (本来 49 就是 1,但是格式是三位,为了不被识别成 492,所以,填充 0 来进行补全)

Lua 5.3 开始支持使用转义序列 u{hhhhhh} 来声明 UTF-8 字符,花括号中支持任意有效的十六进制:

---------------------------

> "u{3b1} u{3b2} u{3b3}"

α β γ

>

---------------------------

3.2 长字符串/多行字符串

page = [[

<html>

<head>

</head>

</html>

]]

有时候字符串中可能存在 a = b[c[i]] ,这样的话,就会出现结束符,导致字符串出现问题,我们可以将边界字符串中加入等号

[===[

this is a string !

]===]

对于注释同样有效,比如

--[=[

]=]

对于一些特殊字符,长字符串中可能会出现问题,所以可以使用转义的方式进行,比如 x00x01.....

但是如果太长了还是需要换行,这会出现问题,Lua 5.2开始,引入了转义序列 z ,该转义符会跳过其后的所有空白字符的,知道遇到第一个非空白的字符。

3.3 强制类型转换

Lua 在需要数值的时候,会把字符串转为数值

Lua 在需要字符串的时候,会把数值转换为字符串

----------------------

> print(10 .. 20)

1020

>

----------------------

这里需要注意一下,10 与 . 之间一定要有空格,不然就会把第一个点认为成小数点

将一个字符串转换为数字除了自动类型转换外,可以使用 tonumber 函数,如果字符串转换不成会返回 nil

> tonumber(" -3 ")

-3

> tonumber(" 10e4 ")

100000.0

>

3.4 字符串标准库

string.xxxx

-- 获取字符串长度

string.len(str)

-- 将一个字符串重复n遍

string.rep(s, n)

-- 将字符串反转

string.reverse(str)

-- 字符串大小写

string.upper / string.lower

-- 提取字符串

string.sub(s, i, j)

从字符串 s 中提取 第i个到第j个字符串,包括 第i个字符串和第j个字符串。字符串的第一个索引为 1

-------------------------------

> s = "Let us hack the planet"

> string.sub(s, 2, 6)

et us

> string.sub(s, 2, -2)

et us hack the plane

> string.sub(s, -1, -1)

t

> string.sub(s, 2, 2)

e

>

---------------------------------

-- 字符转换

string.char() 接受 0 个或多个整数作为参数,然后将每个整数转换成对应的字符,最后返回由这些字符连接而成的字符串

string.byte(s, i) 返回字符串s的第i个字符的内部数值表示,该函数第二个参数是可选的。直接调用 string.byte(s) 那么会返回第一个字符的内部数值,其实就是ascii值

-- 格式化字符串

string.format 用于进行字符串格式化和将数值输出为字符串

一个指示符由一个百分号和一个代表格式化方式的字母组成

d 代表一个十进制整数

x 代表一个十六进制整数

f 代表一个浮点数

s 代表字符串

---------------------------------

> string.format("x = %d y = %d",10, 20)

x = 10 y = 20

> string.format("x = %x", 200)

x = c8

> string.format("x= 0x%X", 200)

x= 0xC8

> string.format("x= %f", 200)

x= 200.000000

> tag, title = "h1", "a title"

> string.format("<%s>%s</%s>", tag, title,tag)

<h1>a title</h1>

>

---------------------------------

3.5 Unicode编码

Lua 为 utf-8 设计了一个标准库 - utf8,用于处理utf-8字符串

---------------------------------

> s = "你好"

> string.len(s)

6

> utf8.len("你好")

2

> utf8.len(s)

2

>

----------------------------------

utf8.char()

utf8.codepoint()

4. 表(Table)

表是Lua中最主要最强大的数据结构。一切皆在表中。表可以表示 数组、集合、记录和其他很多数据结构。Lua语言同样可以表示包和其他对象

当调用函数 math.sin 时,其实在 Lua 语言中,实际含义时以字符串 sin 为键 检索表 math

Lua 语言中的表的本质上是一种辅助数组(associative array),这种数组不仅可以使用数值作为索引,也可以使用字符串或者其他任意类型的值作为索引(nil除外)

创建表非常简单

a = {}

当程序中不再有指向表的引用时,垃圾收集器最终会删除这个表并重用其内存

4.1 表索引

同一个表中可以存在不同类型的键

----------------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> a = {}

> for i = 1 ,1000 do a[i]=i*2 end

> a[8]

16

> a['x'] = "ssss"

> a['x']

ssss

> a['u']

nil

>

-----------------------------------

当把表当作结构体使用的时候,可以把索引当作成员名称进行使用

------------------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> a = {}

> for i = 1 ,1000 do a[i]=i*2 end

> a[8]

16

> a['x'] = "ssss"

> a['x']

ssss

> a['u']

nil

> a.x

ssss

> a.u

nil

>

-------------------------------------

4.2 表构造器

表构造器是用来创建和初始化表的表达式,也是Lua 语言中独有的也是最有用的、最灵活的机制之一。

4.2.1 最简单的构造器就是空构造器 {}

4.2.2 可以采用记录式的方式进行构造

days = {"Sunday", "Monday", "Tuesday", "Wednesday"}

------------------------------------------------------

> days = {"Sunday", "Monday", "Tuesday", "Wednesday"}

> days[1]

Sunday

> days[4]

Wednesday

>

------------------------------------------------------

4.2.3

a = {x = 10, y = 20}

上述代码等价于

a = {}; a.x = 10;a.y = 20

如果想删除一个键值对,那么直接 a.x = nil 就可以了

4.2.3 可以采用列表式的方式进行构造

xlist = {{x=0, y=0}, {x=-1, y=2}, {x=1, y=8}}

--------------------------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> xlist = {{x=0, y=0}, {x=-1, y=2}, {x=1, y=8}}

> xlist[0]

nil

> xlist[1]

table: 0x7fca8b6009a0

> xlist[1][1]

nil

>

> xlist[1][2]

nil

> xlist[2][2]

nil

> xlist[2]["x"]

-1

> xlist[2][y]

nil

>

----------------------------------------------

可以同时使用两种构造方式

----------------------------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> demp = {color="black", num=2, {x=10, y=19}}

> demp[1]

table: 0x7ff07f500bc0

> demp[1].color

nil

> demp.color

black

> demp[num]

nil

> demp["num"]

2

> demp[3][x]

stdin:1: attempt to index a nil value (field '?')

stack traceback:

stdin:1: in main chunk

[C]: in ?

> demp[3]["x"]

stdin:1: attempt to index a nil value (field '?')

stack traceback:

stdin:1: in main chunk

[C]: in ?

> demp[2]

nil

> demp[1].x

10

>

------------------------------------------------

一定要注意,当记录式和列表式同时存在的时候,即使把记录式的构造元素放在前,table[1] 也是指列表式的第一个元素

使用以上两种构造器的时候都有各自的局限,比如对于特殊字符支持得不够,Lua 还有另外一种更加通用的构造器

opnames = {[" "] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}

------------------------------------------------

> opnames = {[" "] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}

> opnames[1]

nil

> opnames[" "]

add

>

-------------------------------------------------

这种方法看起来操蛋,但是非常灵活,可以完美表示两种构造方式

{x = 0, y = 1} <--> {["x"] = 0, ["y"] = 1}

-------------------------------------------------

> demo = {x=0,y=1}

> demo["x"]

0

> demo = nil

> demo = {["x"]=0,["y"]=1 }

> demo["x"]

0

>

-------------------------------------------------

{"r", "g", "b"} <--> {[1] = "r", [2] = "g", [3] = "b"}

-------------------------------------------------

> demo = {"r", "g", "b"}

> demo[1]

r

> demo = nil

> demo = {[1] = "r", [2] = "g", [3] = "b"}

> demo[1]

r

>

--------------------------------------------------

4.3 数组、列表和序列

使用整数作为索引的表可以成为列表或者数组 a = {"a", "b", "c", "d"}

所有元素均不为nil的列表称为序列 b = {a = "a",b = "b"}

对于序列来说,长度操作符 # 的用处就很多了

for i = 1, #a do

print(a[i])

end

print(a[#a]) -- 打印最后一个元素

a[#a] = nil -- 删除最后一个元素

a[#a 1] = v -- 将v添加到序列最后

对于非序列,其中有很多有意思的情况,比如下面

----------------------------------------------------

> a = {}

> a[1] = "dd"

> #a

1

> a[2] = "ff"

> #a

2

> a[99] = "rr"

> #a

2

> for i = 1, #a do

print(a[i])

end

dd

ff

> -----------------

> a = {}

> a = {[1] = "dd", [2] = "ff", [99] = "rr"}

> #a

2

> for i = 1, #a do

print(a[i])

end

dd

ff

>

-------------------------------------------------

4.4 遍历表

Lua 使用 pairs 迭代器遍历表中的键值对

t = {10, print,x = 12, k = "hi"}

for k, v in pairs(t) do

print(k,v)

end

如果只有一个接受参数,那么返回的是 key

--------------------------------------------------

~ ❯❯❯ lua

Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio

> t = {10, print,x = 12, k = "hi"}

> for k, v in pairs(t) do

>> print(k, v)

>>

>> end

1 10

2 function: 0x10707fd0f

k hi

x 12

> for k, v in ipairs(t) do

print(k, v)

end

1 10

2 function: 0x10707fd0f

>

--------------------------------------------------

由于 Lua 的底层实现,遍历数组过程中元素出现顺序是随机的

对于列表而言,可以使用 ipairs 迭代器

t = {10, print, 12, "hi"}

for k, v in ipairs(t) do

print(k, v)

end

----------------------------------------------------

> t = {10, print,12, "hi"}

> for k, v in ipairs(t) do

print(k, v)

end

1 10

2 function: 0x10707fd0f

3 12

4 hi

>

----------------------------------------------------

这种情况下,列表是按照顺序打印的

另一种遍历序列的方法是使用数值型for循环:

t = {10, print, 12, "hi"}

for k = 1, #t do

print(k, t[k])

end

---------------------------------------------------

> t = {10, print,12, "hi"}

> for k = 1,#t do

>> print(k, t[k])

>> end

1 10

2 function: 0x10707fd0f

3 12

4 hi

>

---------------------------------------------------

4.5 安全访问

如果我们想确认在执行的库中是否存在某个函数,可以使用

if lib and lib.foo then ...

如果我们确认库是存在的,那么可以直接

if lib.foo then ...

但是如果表的嵌套深度比较深,那就容易很长,而且容易出现问题

这里可以使用逻辑代码实现其他语言的安全访问操作符

(a or {}).b

如果 a 为 nil,那么整体为 nil,不会爆出异常

4.6 表的标准库

-- 插入元素

t = {10, 20, 30}

table.insert(t, 1, 15)

之后 t 变为了 {15, 10, 20, 30}

------------------------------------------------

> t = {10, 20, 30}

> table.insert(t, 1, 15)

> for k, v in ipairs(t) do

>> print(k, v)

>> end

1 15

2 10

3 20

4 30

> table.insert(t, 44) -- 如果默认不使用位置参数,就会在最后插入值

> for k, v in ipairs(t) do

print(k, v)

end

1 15

2 10

3 20

4 30

5 44

>

----------------------------------------------------

-- 删除元素

table.remove(t, pos, foo)

同样的,删除指定位置的元素,后续元素向前移动填充,不使用未知参数的话默认删除最后一个

借助这两个函数,可以完成栈、队列、双端队列

栈:(stack)—— 先进后出,删除与加入均在栈顶操作

堆栈中两个最重要的操作是PUSH和POP,两个是相反的操作。

PUSH:在堆栈的顶部加入一个元素。PUSH操作可以使用 table.insert(t,x) 实现

POP:在堆栈顶部移去一个元素, 并将堆栈的大小减一。可以使用 table.remove(t) 实现

-- 移动元素

table.move 可以移动元素

table.move(a, f, e, t) 将a表中 f到e 的元素,包括f、e移动到位置 t 上

a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

table.move(a, 1, #a, 2)

------------------------------------------------------

> a = {"a", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

> table.move(a, 1, #a, 2)

table: 0x7fae4b601cb0

> for k, v in pairs(a) do

print(k, v)

end

1 a

2 a

3 1

4 2

5 3

6 4

7 5

8 6

9 7

10 8

11 9

12 10

>

-------------------------------------------------------

可以看到,将所有的元素向后移动一位时候,第一位还是原来的,只不过后续被相继覆盖

5. 函数

Lua 中当函数只有一个参数,且参数为字符串常量或者表构造器的时候,括号是可选的,例如

print "Hello World"

dofile 'demo.lua'

print [[ demo ]]

f{x = 10 , y = 20}

type{}

Lua 面向对象编程的语法为

o:foo(x) 调用对象 o 的foo方法

Lua 程序既可以调用 Lua 函数,也可以调用 C语言编写的函数。一般使用 C 语言编写对性能要求高或者一些操作系统方面的操作

一个小demo

function add(a)

local sum = 0

for i = 1,#a do

sum = sum a[i]

end

return sum

end

当调用的参数与函数定义的参数不同的时候,Lua 会自动进行舍弃,或者设置 nil

function f(a, b) print(a, b) end

f() --> nil nil

f(3) --> 3 nil

f(3, 4) --> 3 4

f(3, 4, 5) --> 3 4

---------------------------------------------------

> function f(a, b) print(a, b) end

> f()

nil nil

> f(3)

3 nil

> f(3, 4)

3 4

> f(3, 4, 5)

3 4

>

----------------------------------------------------

5.1 多返回值

Lua 允许一个函数返回多个值,很多预定义函数就是这样,比如string.find()

s, e = string.find("hello Lua users", "Lua")

print(s, e)

---------------------------------------------------

> s, e = string.find("hello Lua users", "Lua")

> print(s, e)

7 9

>

----------------------------------------------------

5.2 可变长参数函数

Lua 中可变长参数用 ... 来表示

function add(...)

local s = 0

for _, v in ipairs(...)

s = s v

end

return s

end

print(3,4,5,6)

5.3 函数table.unpack

这个函数的参数是一个数组,返回值为所有元素

a, b = table.unpack{10,20,30} -- 此时 30 就会被丢弃

--------------------------------------------------

> table.unpack{10,20,30}

10 20 30

> a, b = table.unpack{10, 20, 30}

> a, b

10 20

>

----------------------------------------------------

pack 函数与unpack 正好相反,把参数转换为列表

--------------------------------------------------

> table.pack(10,20,30)

table: 0x7fae4b601580

> for i in ipairs(a[1]) do

print(a[1][i])

end

10

20

30

>

------------------------------------------------------

可以看到其实转换后的列表其实是这样的 a{{10,20,30}}

5.4 尾调用

function f(x) x = x 1; return g(x) end

f调用了g函数后就没有其他操作了,这样的话,当执行完g函数,返回直接直接给f的调用者,内存不再管 f 函数,这样减少栈的占用

6. 输入输出

6.1 简单 I/O 模型

I/O 库把当前输入流初始化为进程的标准输入(C语言的stdin),将当前的输出流初始化为进程的标准输出(C语言中的stdout)

当执行 io.read() 这样的语句时,就可以从标准输入中读取一行

6.1.1 函数的 io.input 和 io.output 可以改变当前的输入输出流

io.input(filename) 会以只读模式打开指定文件,并将文件设置为当前输入流,之后所有的输入流都将来自该文件,除非再次调用 io.input

io.output 也是一样

6.1.2 io.write

io.write 这个函数可以读取任意数量的字符串或数字,并将其写入到当前的输出流

一般只在调试代码中才使用print,当需要完全控制输出时,应使用函数io.write()

<> io.write 与 print 函数的不同

1) io.write 不会在最终的输出t中添加诸如制表符或换行符这样的额外内容

2) io.write 函数允许对输出进行重定向,而print() 只能使用标准输出

3) print 函数可以自动为其参数调用 tostring,调试起来方便了,但是容易产生bug

io.write 函数在将数值转换为字符串时,要完全控制,应该使用format

io.write(string.format("sin(3)= %.4f", math.sin(3)))

------------------------------------------------------

> io.write(string.format("sin(3)= %.4f", math.sin(3)))

sin(3)= 0.1411file (0x7fff9a0d01a8)

------------------------------------------------------

6.1.3 io.read()

函数 io.read 可以从当前输入流中读取字符串,其参数决定了要读取的数据

"a" : 读取整个文件

"l" : 读取下一行(丢弃换行符)

"L" : 读取下一行(保留换行符)

"n" : 读取一个数值

num 以字符串读取 num 个字符

调用 io.read("a") 可以从当前位置开始读取当前输入文件的全部内容,如果当前位置处于文件的末尾或文件为空,那么该函数返回一个空字符串

由于Lua 语言对于长字符串处理非常好,所以可以读取一个文件的全部内容,之后集中进行处理,一个模型如下

t = io.read("a") -- 读取整个文件

t = string.gsub(t, "bad", "good")

io.write(t) -- 输出结果

6.1.4 io.lines()

如果要逐行迭代一个文件,那么使用 io.lines() 迭代器会更简单:

for line in io.lines() do

xxxx

end

6.2 完成 I/O 模型

使用 io.open(filename, mode) 打开一个文件

mode 字符串包括:

r (只读)

w (只写)

a (追加)

b (打开二进制文件)

这个函数的返回值为文件对应的流,当发生错误时,返回 nil 和错误信息

检查错误的一种典型方法是使用函数 assert

local f = assert(io.open(filename, mode))

如果 io.open 执行失败,那么错误信息会作为函数 assert 的第二个参数被传入,之后函数assert会将错误信息展示出来

打开文件后,可以使用read 和write 方法从流中读取和向流中写入。他们与函数read / write 类似,但是需要使用冒号1运算符将他们当做流对象的方法来调用

local f = assert(io.open(filename, "r"))

local t = f:read("a")

f:close()

I/O 库提供了三个预定义的C语言流的句柄: io.stdin io.stdout io.stderr

op.stderr:write(message)

这样可以将信息直接写入到标准错误流中

io.input 和 io.output 允许混用简单和完整 I/O 模型

调用无参数的io.input 可以获取当前输入流,调用 io.input(handle) 可以设置当前输入流(类似的调用同样适合于函数 io.output)

例如:想要临时改变当前输入流,可以像这样

local temp = io.input() -- 保存当前输入流

io.input("newinput") -- 打开一个新的当前输入流

对新的输入流进行操作

io.input():close() -- 关闭当前流

io.input(temp) -- 恢复此前的当前输入流

注意:io.read(args) 实际上是 io.input():read(args) ,也就是说 read函数是在当前输入流上的

同样 io.write(args) 实际上是 io.input():write(args) 的缩写

一些系统调用

os.exit() 终止程序运行

os.getenv("HOME") --> /home/lua 获取环境变量的值,如果未定义,返回nil

os.execute 用于执行系统命令,返回值为命令执行结束后的状态,第一个返回值是一个boolean的值, true 表示执行成功

os.execute 第二个返回值为 字符串,exit 表示程序正常运行结束, signal 表示因为信号而中断

第三个返回值是返回状态

os.popen 也可以执行一条系统命令,而且这个函数还可以重定向输入输出,从而使程序可以向命令中写入和从输出中读取

这个挺强大的,后期使用再进行探究

7. 局部变量和代码块

7.1 局部变量和代码块

Lua 语言的变量默认情况下是全局变量 ,所有的全局变量在使用前必须声明

局部变量生小范围仅限于声明他的代码块

使用 local 来建立局部变量

7.2 控制结构

7.2.1 if then else

-------------------------

if op == " " then

r = a b

elseif op == "-" then

r = a - b

else

error("invaild operation")

end

-------------------------

7.2.2 while

-------------------------

local i = 1

while a[i] do

print(a[i])

i = i 1

end

--------------------------

7.2.3 repeat

-------------------------

-- 输出第一个非空的行

local line

repeat

line = io.read()

until line ~= ""

print(line)

--------------------------

与其他编程语言不同的是,Lua 语言中,循环体内声明的局部变量的作用域包括测试条件

例如:

-------------------------

local sqr = x / 2

repeat

sqr = (sqr x/sqr) / 2

local error = math.abs(sqr^2 - x)

until error < x/10000 -- 此时局部变量 error 仍然可见

7.2.4 数值型 for

for循环分为 数值型 和 泛型

数值型如下

-------------------------

for var = exp1, exp2, exp3 do

dosth

end

--------------------------

循环到 exp1变化到exp2,每次变化的步长为 exp3

如果不想设置循环上线,可以将exp3 设置为 math.huge

7.2.5 泛型 for

泛型for遍历迭代函数返回的所有值

以后再细究

7.3 break、return和 goto

break 和 return 语句用于从当前循环结构中跳出,goto 语句允许跳转到函数中的几乎任何地方

我们可以使用break语句结束循环,中断后,程序会紧接着被中断的循环继续执行

goto语句用于将当前程序跳转到相应的标签处继续执行,存在一些争议,暂不使用

8. 模式匹配

Lua 中没有正则表达式,但是有一个模式匹配

8.1 模式匹配相关函数

字符串标准库提供了四个函数

find

gsub

match

gmatch

8.1.1 string.find

在指定的目标字符串中搜索指定的模式,比如搜索一个单词

string.find 存在四个参数:字符串、查找的模式、位置、是否简单检索

其中简单检索就是不在乎模式,直接查找字符串

函数返回两个值,即匹配到的开始和结束值,没有匹配到返回nil

s = "hello world"

i, j = string.find(s, "hello")

-------------------------------

> s = "hello world"

> string.find(s, "hello")

1 5

>

-------------------------------

8.1.2 string.match

string.match 与 find 差不多,只不过返回的是目标字符串中与模式相匹配的那部分子串,而且非该模式所在的位置

-------------------------------

> s = "hello world"

> string.match(s, "hello")

hello

>

--------------------------------

这个函数与变量结合起来后,就像正则了

---------------------------------

> data = "Today is 28/11/2019"

> string.match(data, "%d /%d /%d ")

28/11/2019

>

----------------------------------

8.1.3 函数 string.gsub

存在三个参数,目标字符串、模式和替换字符串,第三个参数可以是函数或一个表。基本语法是将目标字符串中所有出现模式的地方替换为字符串

s = string.gsub("Lua is cute", "cute", "great")

----------------------------------

> string.gsub("Lua is cute", "cute", "great")

Lua is great 1

-----------------------------------

> string.gsub("Lua is cute vv cute", "cute", "great")

Lua is great vv great 2

------------------------------------

8.1.4 函数 string.gmatch

这个函数会返回一个函数,通过返回的函数可以遍历一个字符串中所有出现的指定模式

s = "some string"

words = {}

for w in string.gmatch(s, "%a ") do

words[#words 1] = w

end

8.2 模式

Lua 语言中使用 百分号作为转义符,被转义的非字母则代表其本身

参照表如下:

. 任意字符

%a 字母

%c 控制字符

%d 数字

%g 除空格外的可打印字符

%l 小写字母

%p 标点符号

%s 空白字符

%u 大写字母

%w 字母和数字

%x 十六进制数字

魔法字符

重复一次或多次

* 重复 0 次或多次

- 重复 0 次或多次(最小匹配)

? 可选(出现0次或者一次)

% 转义符

[] 自定义字符集,比如 [%w_] 匹配所有下划线结尾的字母或数字

[a-z0-9A-F]

^ 在 [] 中表示补字符,表示字符集的补集 [^n] 除换行符以外的其他字符

^$ 开始结束符

.* 会尽可能长的匹配

.- 会最小匹配

8.3 捕获

pair = "name = Anna"

key, value = string.match(pair, "(%a )%s*=%s*(%a )")

print(key, value)

8.4 替换

使用string.gsub

8.4.1 URL 编码

Lua 中没有 URL编解码的函数

8.4.2 制表符展开

没啥帮助

9. 位和字节(没理解)

9.3 打包和解包二进制数据

二进制数和基本类型值可以通过函数相互转换

string.pack 把值打包成字符串

string.unpack 从二进制字符串中提取值

10. 数据结构

Lua 中数组、多维数组、链表、队列、集合与包

11. 序列化

其实吧,序列化就是把一段代码,一段字符,一个数据等等转化为字符串进行保存。

12. 编译、执行和错误

12.1 编译

dofile 函数的工作核心是 loadfile 函数,这个函数是从文件中加载 Lua 代码段,但他不会执行代码段,只是编译代码,之后将内容作为一个函数来进行返回

其实就是等价于

----------------------------

function dofile()

local f = assert(loadfile(filename))

return f()

end

----------------------------

如果 loadfile 函数执行失败会抛出一个异常,所以 loadfile 使用起来会更加灵活

load 函数与 loadfile 函数类似,不同之处在于load 函数是从一个字符串或者函数中去读取代码段,而不是从文件中读取。

----------------------------

> f = load("i = i 1")

> i = 0

> f()

> print(i)

1

> f()

> print(i)

2

>

----------------------------

> d = load("i = i 1 ; print(i);")

> i = 2

> d()

3

-------------------------------

12.2 预编译的代码

Lua 语言会在运行源代码之前进行预编译,Lua语言也允许我们以预编译的形式分发代码

生成预编译文件(二进制文件)最简单的方式就是使用 lua 自带的 luac 程序。例如

luac -o prog.lc prog.lua

这样就会生成 prog.lc 这个二进制文件,loadfile和load 都可以接受预编译代码

其实 luac 等价于下面的代码

-------------------------------

p = loadfile(arg[1])

f = io.open(arg[2], "wb")

f:write(string.dump(p))

f:close()

--------------------------------

这里比较关键的函数是 string.dump ,这个函数的传入参数是一个Lua函数,返回值是传入函数对应的字符串形式的预编译代码。

12.3 错误

Lua 语言作为一种嵌入式语言,每次产生错误不能把程序停下来,所以Lua的错误必须处理

我们可以使用 error 函数来传入一个错误信息

---------------------------------

print("enter a nunmber")

n = io.read("n")

if not n then error("invavid input") end

-----------------------------------

这种错误的情况太多,所以Lua 提供了assert函数,这个函数可以完成相关操作

print "enter a number"

n = assert(io.read("*n"), "invalid input")

这个函数检查第一个参数是否为真,如果为真返回第一个参数,如果为假则引发一个错误

以打开文件为例 io.open,打开一个不存在的文件会导致错误,返回false,可以如下处理

----------------------------------

local file, msg

repeat

print("enter a file name")

local name = io.read()

if not name then return end

file, msg = io.open(name, "r")

if not file then print(msg) end

until file

---------------------------------------

如果不想处理这些情况又想安全地运行程序,那么只需使用 assert:

file = assert(io.open(name, "r"))

12.4 错误处理和异常

如果要在一段Lua 代码中处理错误,那么应该使用函数 pcall来封装代码

想要捕获执行中发生的所有错误,那么首先需要将这段代码进行封装到一个函数中,这个函数通常是一个匿名函数,之后通过pcall来调用这个函数

---------------------------------------

local ok, msg = pcall(function ()

some code

if unexpected_condition then error() end

some code

print(a[i]) -- 因为 a 不可能是一个表,所以这是一个错误

some code

end)

if ok then

regular code

else

error-handling code

end

-----------------------------------

函数pcall 会以一种保护模式来调用它的第一个参数,以便捕获该函数执行的错误,无论是否有错误产生,函数pcall都不会产生错误

如果没有错误产生会返回 true 以及被调用的函数的所有返回值,否则返回false 以及错误信息(错误信息是一个对象)

13. 模块与包

模块可以使用 require 函数加载,并且保存在本地的变量中(交互模式中是不可以的)

local m = require "math"

print(m.sin(3.14))

13.1 函数 require

这个函数可以完成模块的加载,如果只有一个参数的时候可以省略括号

local m = require "math"

local m = require('math')

都是可以的

require 函数导入包的过程如下

1. require 函数先在表 package.loaded 中检查模块是否已被加载。如果模块已经被加载,函数require 就会返回相应的值,所以一旦模块被加载过,那么后续所有对于这个模块的的 require 都会返回相同的值

2. 如果模块尚未加载,那么函数 require 则搜索具有指定模块名的Lua 文件(搜索路径由变量package.path 指定),如果函数 require 找到了相应的文件,那么就用函数 loadfile 将其进行加载,结果是一个我们称之为加载器的函数

3. 如果require 找不到相应的模块名的 Lua 文件,那么它就搜索相应名称的 C 标准库(查找路径由 package.cpath 来进行指定),如果找到了一个 C 标准库,则使用底层函数 package.loadlib 进行加载,这个底层函数会查找名为 luaopen_{modname}的函数。其中括号中的内容会随着函数而变。加载函数就是 loadlib 的执行结果,也就是一个被表示为 Lua 函数的C语言函数 luaopen_xxxx

无论require 哪种模块,如果加载函数有返回值,那么require 会返回这个值,然后将其保存在表 package.loaded 中,以便未来加载

如果要强制函数 require 加载同一个模块两次,可以先将模块从 package.loaded 中删除

package.loaded.modname = nil

13.1.1 模块重命名

如果我们需要导入不同版本的一个模块,那么我们可以采用重命名的方式

如果是 Lua 的模块,那么直接修改模块的文件名就可以了。

C语言的就比较复杂了,需要用连字符,需要再查找吧。

13.1.2 搜索路径

Lua 查找模块是使用一个叫模板的东西,其实就是一段带有可选问号的文件名

-----------------------

> package.path

/usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua

> package.cpath

/usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so

>

-----------------------

查找时候会用 模块名去替换其中的问号,之后进行匹配

13.1.3 搜索器

13.2 Lua 语言模块编写基本方法

最简单的方法是 创建一个表并将所有需要导出的函数放入其中,最后返回这个表

--------------------------

local M = {}

local function new(r, j)

return {r=r, j=j}

end

M.new = new

M.i = new(0, 1)

return M

--------------------------

还有一种方法

--------------------------

local function new(r, j) return {r=r, j=j} end

-- 定义常量 ‘i’

local i = complex.new(0, 1)

local function add(c1, c2)

return new(c1.r c2.r, c1.i c2.i)

end

return {

new = new,

i = i,

add = add

}

-------------------------

14. 元表和原方法

元表是面向对象领域的受限制类,元表定义的是实例的行为,比如两个表相加

Lua 中每一个值都可以有元表,每一个表和用户数据类型都具有各自独立的元表,而其他类型的值则共享对应类型所属的同一个元表

可以使用 getmetatable(obj) 来获取 obj 的元表

------------------------

> obj = {}

> getmetatable(obj)

nil

>

--------------------------

可以使用 setmetatable 来进行设置或者修改任意表的元表

t = {}

t1 = {}

setmetatable(t, t1) -- 设置 t1 为 t 的元表

print(getmetatable(t) == t1)

--------------------------

> t = {}

> t1 = {}

> setmetatable(t, t1)

table: 0x7fb707602110

> getmetatable(t)

table: 0x7fb7075011d0

> getmetatable(t1)

nil

> getmetatable(t) == t1

true

>

---------------------------

14.1 算数运算相关的元方法 (暂时用不到)

原方法中定义了一些固定的方法,比如加减法等,可以使用赋值的方法给这些默认操作赋值一个匿名函数,这样下次调用这些固定方法时直接调用匿名函数

14.2 表相关的原方法

14.2.1 __index 元方法

当访问一个表中不存在的字段时,会返回nil。这些访问引发解释器查找一个名为 __index 的元方法,如果没有这个方法就会返回 nil,否则由这个元方法来提供结果

下面演示一下继承操作

-- 创建具有默认值的原型

prototype = {x=0, y=0, width=100, height=100}

声明一个构造函数,让构造函数创建共享同一个元表的新窗口

local mt = {} -- 创建一个元表

-- 声明构造函数

function new (o)

setmetatable(o, mt)

return 0

end

现在定义一个元方法 __index :

mt.__index = function(_, key)

return prototype[key]

end

创建一个新的窗口,并查询一个创建时没有指定的字段

w = new{x=10, y=20}

print(w.width)

此时会发现,虽然 w 没有定义 width ,但是可以直接调用

Lua 语言使用元方法 __index 字段来实现继承

__index 方法可以赋值一个函数,同时可以是一个表

上面函数也等价于

mt.__index = prototype

这样在查找 mt 的 __index 方法时,就会直接在这个表中查找

mt["width"] == prototype["width"]

14.2.2 __newindex 元方法

这个元方法用于表的更新

15. 面向对象编程

Lua 的一张表就是一个对象。首先,表和对象一样,可以拥有状态

例如

Account = {balance = 0}

function Account.withdraw(v)

Account.balance = Account.balance - v

end

上面的代码创建了一个新函数,并将该函数存入 Account 对象的 withdraw 字段,之后我们可以如下调用

Account.withdraw(100.00)

这种函数差不多就是所谓的方法了,不过在函数中使用全局变量是不好的习惯,我们需要使用 self 或 this 来进行代替

function Account.withdraw(self, v)

self.balance = self.balance - v

end

调用该方法时,必须要制定操作对象

a1 = Account; Account = nil

a1.withdraw(a1, 100.00)

Lua 语言中使用冒号操作符隐藏了 self 参数

------------------------------------

function Account:withdraw(260.00)

self.balance = self.balance - v

end

-------------------------------------

冒号和点都可以进行定义和调用

-------------------------------------

Account = {

balance = 0,

withdraw = function(self, v)

self.balance = self.balance - v

end

}

function Account:deposit(v)

self.balance = self.balance v

end

Account.deposit(Account, 200.00)

Account:withdraw(100.00)

--------------------------------------

15.1 类 class

Lua 本身是没有面向对象的概念的,如果 Nmap使用了再回来学习

16. 环境

Lua 语言中没有全局变量的概念,还是通过模拟的方式来实现

Lua 使用一个表来进行记录全局变量,之后又将这个表保存在全局变量 _G 中

例如输出所有的全局变量的名称

for n in pairs(_G) do print(n) end

17. 垃圾管理

Lua 语言使用自动内存管理。Lua 语言通过垃圾收集自动地删除成为垃圾的对象,可以解决无效指针和内存泄漏

18. 协程

线程可以多个一同使用,但是协程不一样,同时只能运行一个 ,只有当前运行的协程被挂起后,执行才会暂停

18.1 协程基础

Lua 语言中所有的协程相关的所有函数都放在表 coroutine 中

函数 create 用于创建一个协程,这个函数只有一个参数,这个参数就是要执行的函数,也就是一段代码块,返回一个 thread 类型的值

co = coroutine.create(function () print("hi") end)

一个协程一般有四种状态,挂起(suspended)、运行(running)、正常(normal)、和dead

我们可以使用 status 来进行查看

当一个协程被创建的时候,它将处于挂起状态,即协程不会在被创建时自动运行

函数 coroutine.resume() 来将一个协程由挂起状态改为运行状态

这是因为在交互模式下执行的原因,我们可以使用分号来阻止输出函数 resume 的返回值 (测试并未成功)

协程强大之处在于 yield 函数, 这个函数可以使运行中的函数挂起,在后续再继续运行

协程是这样看到 yield 的,一旦遇到yield,协程就会被挂起,整个挂起期间都是在执行 yield 函数期间,直到遇到一个拯救它的 resume ,yield 函数才执行结束,同时返回函数的返回值。

但是,即使 resume 把它苏醒又能怎样,还不是等待下一个 yield 或者结束

当协程中的函数已经执行完毕了,此时就会出现协程属于 dead ,此时再resume,就会返回false,并且报错

像函数 pcall 一样,resume 函数也是运行在保护模式中,因此,协程如果执行过程中出错,Lua 语言不会显示错误信息,而是将错误信息返回给函数 resume

当协程A唤醒协程B的时候,协程A既不是挂起状态,也不是运行状态(因为正在运行的是B),此时协程A的状态为 正常状态

Lua 语言中一个非常有用的机制是通过一对resume和yield 来交换数据,第一个resume 函数会把所有的额外参数传递给协程的主函数

19. 反射

反射是程序用来检查和修改自身某些部分的能力

调试库由两类函数组成,自省函数和钩子

自省函数允许我们检查一个正在运行中的程序的几个方面,例如活动的栈、当前正在执行的代码行、局部变量的名称和值

钩子则允许我们追踪一个程序的执行

调试库并不是所谓的debugger,而是实现debugger的底层技术

19.1 自省机制

调试库中主要的自省函数 getinfo , 该函数的第一个参数可以是一个函数或一个栈层次,当某个函数 foo 调用 debug.getinfo(foo) 时,该函数会返回一个包含与该函数有关的一些数据的表。这个表可能具有以下字段 :

source : 说明函数定义的位置,如果定义在字符串中(调用load),那么字段的值为这个字符串,如果被定义在文件中,那么就是这个函数所在的文件名

short_src : 该字段是source 的精简版本(最多60个字符),对于错误信息十分有用

linedefined : 函数定义在源代码中的第一行的行号

lastlinedefined : 该字段是该函数定义在源代码中最后一行的行号

what : 用于说明函数的类型,普通函数就显示lua,C语言函数就显示 C,主函数就是 main

name : 该字段是该函数的一个适当的名称,例如保存该函数的全局变量的名称

namewhat : 该字段用于说明上一个字段的含义,可能是 global , local , method, field 或 空字符串 ,空字符串表示 Lua 语言找不到该函数的名称

nups : 该字段是该函数的上值的个数 (不理解是干啥的)

nparams : 该字段是该函数的参数个数

isvararg : 该字段表明该函数是否为可变长参数函数

activelines : 该字段是一个包含该函数所有活跃行的集合。

func : 该字段是函数本身

当一个 foo 是一个 C 函数时,只有 what,name,namewhat,nups和func 是有意义的

当一个数字 n 作为参数调用函数 debug.getinfo(n) 时,可以得到有关相应栈层次上活跃函数的数据

栈层次是一个数字,代表某个时刻上活跃的特定函数

调用getinfo 的函数 A 的层次是 1,而调用 A 的函数的层次是 2 ,以此类推( 0 层次是 C 函数 getinfo本身)

每次都获取一堆值也不好,效率太低,所以 getinfo 提供了第二个参数

第二个参数是一个字符串

n --> name & namewhat

f --> func

S --> source, short_src, what, linedefined, lastlinedefined

l --> currentline

L --> activelines

u --> nup, nparams, isvararg

19.1.1 访问局部变量

debug.getlocal

我们可以通过debug.getlocal 来检查任意活跃函数的局部变量。该函数有两个参数,一个是要检查的栈层次,另一个是变量的索引。

这个函数有两个返回值,变量名和变量的当前值

如果 变量索引大于活跃变量的数量,函数返回 nil ,如果栈层次无效,则会抛出异常

Lua 语言按局部变量在函数中出现的顺序对它们进行编号,但编号只限于在函数当前的作用域中活跃的变量。

可以看到,并没有变量 c 被打印出来,因为 do local c = a - b end 将c的作用域限定得死死的,不在外部函数内,所以不在 debug.getlocal 范围内

我们稍微改变一下

可以看到,打开 c 的作用域后就可以被 debug.getlocal 发现了

debug.setlocal 可以用来设置局部变量的值,前两个参数是栈层次和变量索引,第三个参数是该局部变量的新值

19.1.2 访问非局部变量

(对实际意义不大)

19.2 钩子(Hook)

调试库中的钩子机制允许用户注册一个钩子函数,这个钩子函数会在程序运行中某个特定事件发生时被调用。有一下四种函数能够触发一个钩子:

1) 每当调用一个函数时产生的 call 事件

2) 每当函数返回时产生的 return 事件

3) 每当开始执行一行新代码时产生的 line 事件

4) 执行完指定数量的指令后产生的 count 事件。(这里的指令指的是内部操作码)

Lua 语言用一个描述导致钩子函数被调用的事件的字符串为参数来调用钩子函数,包括 call , return , line , count

对于事件 line 来说,还有第二个参数,即新行号。我们可以在钩子函数内部调用函数 debug.getinfo() 来获取更多信息。

要注册一个钩子,需要用两个或三个函数来调用函数 debug.sethook :

第一个参数是钩子函数

第二个参数是描述要监控事件的掩码字符串。

第三个参数是一个用于描述以何种频度获取count 事件的可选数字。

如果要监控call, return ,line 事件,那么需要把这几个事件的首字母 c, r 或 l 放入掩码字符串,如果监控 count 事件,则只需要在第三个参数指定一个计数器

如果要关闭钩子,只需不带任何参数的调用函数 sethook 即可

20. 协程加调度器实现多线程

太遗憾了,Lua 没有多线程机制,只有协程,使用协程来实现多线程,这太蠢了,先看看Nmap 那边有没有为我们提供多线程的方法吧,没有的话再回来重新学习协程实现多线程

0 人点赞