Lumesh Shell 语法手册
一、变量与类型
1. 变量声明与赋值
声明变量:使用
let
关键字,支持多变量声明。类型根据赋值自动分配。let x = 10 # 单变量
let a, b = 1, "hello" # 多变量分别赋值(右侧表达式数量必须匹配)
let a, b, c = 1 # 多变量统一赋值
let a = b = 1 # 多变量连续赋值赋值操作符:
=
:普通赋值。:=
:延迟赋值(将表达式作为引用存储)。
x := 2 + 3 # 延迟求值,存储表达式而非结果
echo x # 输出表达式: 2+3
eval x # 输出结果: 5删除变量:使用
del
。del x
使用变量:可选$,直接使用
echo a x
。
严格模式需要$: echo $x
- 查看变量类型:
let a = 10
type a # Integer
type a == "Integer" # True
边缘情况:
- 多变量声明时,右侧表达式数量必须与左侧变量数量一致或统一一个值,否则抛出
SyntaxError
。
2. 数据类型
基本类型
类型 | 示例 |
---|---|
整数 | 42 , -3 |
浮点数 | 3.14 , -0.5 , 10% |
字符串 | "Hello\n" , 'raw' |
布尔值 | True , False |
列表(数组) | [1, "a", True] |
映射(字典) | {name: "Alice", age: 30} |
区间 | 1..8 , 1..10 |
时间 | time.parse('2025-5-20') |
文件大小 | 4K ,5T |
空值 | None |
复杂类型
类型 | 示例 |
---|---|
变量 | x |
函数 | fn add(x,y){return x+y} |
lambda | let add = (x,y) -> x + y |
内置库 | math.floor |
作用域规则
- lambda, function函数 将创建子环境作用域。
- 子环境继承父环境变量,不修改父作用域。
字符串
单引号为原始字符串。
双引号支持 文本转义(如
\n
),unicode转义(如\u{2614}
),ansi转义(如\033[34m
)。点引号为字符串模板,支持
$var
变量替换和${var}
子进程语句执行捕获,支持ansi转义字符。print "Hello\nworld!\u{2614}"
Hello #输出两行, \n被转义为换行符
world!☔ #unicode转义,\u{2614}是雨伞字符
let str2 = 'Hello\nworld!'
Hello\nworld! #输出一行,包括\n的原始形式
let a = [1,2,5]
print `a is $a, and a[0] is ${a[0]}`
print "\033[31mRed msg\033[m" #输出红色的Red msg点引号内的变量替换比子进程语句捕获效率高,非必要情况建议使用前者
边缘情况:
单引号字符串中的
\
按字面处理(如'Line\n'
输出Line\n
)。
未定义的转义序列会报错,如:echo "Hello\_"
[PARSE FAILED] syntax error: invalid string escape sequence `\_`
|
1 | echo "Hello\_"
|
文件大小类型:
以整数紧跟单位构成,支持如下单位:
"B" "K" "M" "G" "T" "P"
lumesh将自动识别单位并以人类可读模式输出,例如:
print 3050M 1038B 3000G
# 输出
2.98G 1K 2.93T
文件大小类型可参与运算。
如:
1M > 30K # 返回 True
fs.ls -l | where(size>20K) # 筛选大于20k的文件
日期时间类型
例如:time.parse('2025-5-20')
日期时间类型可参与运算。
如:
time.parse('2025-5-20') > time.parse('2025-1-20') # 返回True |
具体操作请参考内置函数的time
模块
百分数
书写为百分数,会自动识别为浮点数
print 37% 2% + 3 |
百分号紧跟数字后为百分数,数字后空格+%则为模运算
二、运算符
1. 运算符分类与优先级
优先级从高到低(数值越小优先级越高)
优先级 | 运算符/结构 | 示例/说明 |
---|---|---|
1 | 括号 () |
(a + b) * c |
2 | 函数调用、列表 [] |
func(arg) , [1, 2] |
3 | 单目运算符 ! , - , __.. |
!flag , -5 |
4 | 幂运算 ^ |
2 ^ 3 |
5 | 乘除模 * , / , % , _*.. |
a * b % c |
6 | 加减 + , - ,_+.. |
a + b - c |
7 | 比较 == , != , > 等 |
`a > b |
8 | 逻辑与 && |
cond1 && cond2 |
9 | 逻辑或 ` | |
10 | 条件运算符 ? : |
cond ? t : f |
11 | 赋值 = , := ? |
x = 5 , let y := 10 |
12 | 管道 ` | ` |
12 | 重定向 << >> >>! |
date >> /tmp/day.txt |
管道和重定向同级,逻辑运算比管道和重定向优先级更高。
2. 空格规则
运算符类型 | 是否需要空格 | 示例 |
---|---|---|
常规运算符 | 两侧应空格 | a + b , x <= 10 |
非严格模式,部分符号两侧可以不空格 | a+b , x<=10 , a=5 |
|
- 和/ 应空格 |
b-3 为字符串,3- b 为减法 |
|
自定义运算符 | 必须用下划线开头且两侧空格 | x _*+ y , a _?= b |
后缀运算符,必须用双下划线开头且空格 | x __*+ |
|
前缀运算符 | 前面为空格或开始,后面无空格 | !x , -7 |
中缀运算符 | 前后均无空格 | dict.key , 1..9 |
后缀运算符 | 前后均无空格 | func(a) , array[i] , 10M |
未来可能废弃,不推荐使用 | a++ , a-- |
a++ 推荐用 a += 1
因为 – 在shell中通常用于参数传递。所以不建议使用a–
自定义操作符
自定义操作符是以
_
开头的符号,只能包含符号,不能包含数字或字母。自定义单目运算符 以
__
开头,如__+
, 优先级同单目运算符。仅能用于后缀运算。自定义+级运算符 以
_+
开头,如_+%
,优先级同+
-
自定义*级运算符 以
_*
开头,如_*-
,优先级同*
/
let __++ = x -> x + 1;
3 __++
边缘情况:
x++y
非法,x+++y
合法。
3. 特别的操作符
==
带类型相等比较.~=
值相等比较.~~
字符串正则匹配.~:
是否包含(可用于字符串,数组,区间,字典(检测是否包含指定key)).
举例:
5 == "5" # False
5 ~= "5" # True
"abc" ~: "a" # True
0..8 ~: 8 # False
0..8 ~: 8 # True
"abc" ~~ '\d' # False
4. 重载的运算符
加法 +
数字
数字 + 数字 = 数字以高精度相加
1 + 2.5 # → 3.5
数字 + 字符串 = 以数值相加
1 + "2.5" # → 3.5
数字 + 列表 = 一起求和
1 + [2.5,3] # → 6.5
字符串
字符串 + 字符串 = 字符串连接
"1" + 2.5 # → "12.5"
字符串 + 列表 = 一起串联
"1" + [2,3] # → "123"
区间
- 区间 + 整数 = 区间扩充(正数从尾部,负数从头部)
0..8 + 2 # → 0..10
0..8 + (-2) # → 2..8
列表
列表 + 列表 = 列表合并
[1,2] + [3,4,5] # → [1,2,3,4,5]
列表 + 其他 = 向列表插入值
[1] + 2.5 # → [1,2.5]
映射
映射 + 映射 = 映射合并
{a:b} + {c:d} # → {a:b,c:d}
映射 + 其他 = 向映射插入值
{a:b} + c # → {a:b,c:c}
字节
- 字节 + 字节 = 字节追加
- 字节 + 字符串 = 字节追加
其他情况抛出异常
减法 -
数字
数字 - 数字 = 数字以高精度相减
1 - 2.5 # → -1.5
数字 - 字符串 = 以数值相减
1 - "2.5" # → -1.5
字符串
字符串 - 字符串 = 字符串移除(首次出现)
"i am lume" - "a" # → "i m lume"
字符串 - 浮点数 = 字符串移除(首次出现)
"i am lume v4.2" - 4.2 # → "i am lume v"
字符串 - 整数 = 字符串移除(正数从尾部,负数从头部)
"98101" - 1 # → "9801"
"98101" - (-2) # → "101"超出长度则返回空字符串
区间
- 区间 - 整数 = 区间缩放(正数从尾部,负数从头部)
0..8 - 2 # → 0..6
0..8 - (-2) # → -2..8
列表
列表 - 列表 = 列表差集
[1,2,3,4,5] - [3,4,5] # → [1,2]
列表 - 其他 = 列表移除
[1,2,3] - 2 # → [1,3]
映射
映射 - 映射 = 映射差集
{a:b,c:d} - {c:d} # → {a:b}
映射 - 其他 = 映射键移除
{a:b,c:d} - c # → {a:b}
其他情况抛出异常
乘法 *
数字
- 数字 * 数字 = 数字相乘
2 * 2.5 # → 5
- 数字 * 字符串 = 以数值相乘
2 * "2.5" # → 5
字符串
字符串 * 整数 = 字符串重复
"2" * 5 # → "22222"
列表 * 列表 = 矩阵乘法
[[1,2,3],[4,5,6]] * [[7,8],[9,10],[11,12]]
# → 返回
+----------+
| C0 C1 |
+==========+
| 58 64 |
| 139 154 |
+----------+矩阵乘法要求维度正确
如内部有缺失元素,计算时会以0填充列表 * 数字 = 每个元素乘法
[1,2,3] * 2.5 # → [2.5,5,7.5]
除法 /
数字
- 数字 / 数字 = 数字相除
5 / 2 # → 2
5 / 2.0 # → 2.5 - 数字 / 字符串 = 以数值相除
5 / "2" # → 2
5.0 / "2.5" # → 2
列表
- 列表 / 数字 = 每个元素除法
[2,4,6] / 2 # → [1,2,3]
其他 +=
-=
*=
/=
只适用于数字运算。
对于未初始化的,自动初始化为0
和四则运算不同的是,对于非数字会返回None,而不是抛出异常。
如:
a += 1 # → 1
a += [1] # None
边缘情况:
- 除以零会报错。
5. 隐式类型转换
数字之间的运算,自动向高精度类型转换。
不同类型数据相加,总是尝试自动转为第一个数据的类型。
# 非严格模式
3 + "5" # → 8(自动转为整数)
"10" + 2 # → "102" (自动转为字符串)
"10" * 2 # → "1010" (字符串重复)
[1,2,3] + 5 # → [1,2,3,5]
[2,3] - 2 # → [3]
0..8 + 3 # → 0..11
{name:wang, age:18} - name # → {age:18}
三、区间、列表与映射
1. 区间
区间表达式
区间使用..<
(左闭右开) 或..
(为闭区间),左右不能有空格。
支持变量扩展。0..<10 #不包含10
0..10 #包含10
a..b区间表达式支持步进:
:step
0..6:2 # 表示步进为2: [0,2,4,6]
可用于循环、包含检测、数组创建等。
let r = 0..8
for i in r {...} # 比直接在数组上循环效率更高
r ~: 5 # 检测是否包含元素
list.from(r) # 转为数组
2. 列表(数组)
列表用
[ ]
表示。内部元素是有序的。也可以用
...
或...<
直接从区间创建0...5 # 输出[0,1,2,3,4]
# 等同于
list.from(0..5)
两个点..
..<
创建区间,三个点...
...<
创建数组
索引用
.
或[i]
表示。let arr = [10, "a", True]
索引和切片
# 基础索引
arr.1
arr[0] # → 10
# 切片操作
arr[1:3] # → ["a", True](左闭右开)
arr[::2] # → [10, True](步长2)
arr[-1:] # → True(切片支持负数索引)复杂嵌套
# 复杂嵌套
[1,24,5,[5,6,8]][3][1] # 显示6
# # 修改元素
# arr[2] = 3.14 # → [10, "a", 3.14]高级操作
参考 list 模块。
边缘情况:
- 数组索引如果超出边界,会触发
out of bounds
错误 - 数组切片支持负数,表示从后往前数的index
- 如果对不能索引的对象进行索引,会触发如下错误:
[ERROR] type error: expected indexable type (list/dict/string), found symbol
3. 映射(字典)
映射用
{}
表示let mydict = {name: "Alice", age: 30}
字典索引
# 基础访问
mydict["name"] # → "Alice"
mydict.name # → "Alice"(简写形式)
# 动态键支持
let key = "ag" + "e"
dict[key] # → 30
# 嵌套访问
let data = {user: mydict}
data.user.age # → 100高级操作
参考 map 模块。
边缘情况:
场景 | 行为 |
---|---|
访问不存在的数组索引 | 触发[ERROR] key x not found in map 错误 |
对非字典对象进行索引 | 触发 [ERROR] not valid index option 错误 |
对未定义的符号进行索引 | 返回字符串,因为shell中操作文件名是最常见的操作 |
对未定义的符号进行索引 | 返回字符串,因为shell中操作文件名是最常见的操作 |
四、语句
语句块
用{}
表示。一般用于流程控制语句。语句组(子命令捕获)
用括号表示子命令,子命令不新建进程,不隔离变量作用域。
echo (len [5,6])
语句
用;
或enter
分割
换行符:
;
或回车。续行符:使用
\
+ 换行符跨行书写。let long_expr = 3 \
+ 5 # 等价于 "3 + 5"
let long_str = "Hello
World" # 等价于 "Hello\n World"注意:引号内的内容无须续行符。
- 注释
注释以#
开头
五、控制结构
条件语句
If 条件
支持嵌套:
`if cond1 { ... } else if cond2 { ... } else { ... }`
不使用 then
关键字,代码块用 {}
包裹。
if True {1} else {if False {2} else {3}}
if x > 10 {
print("Large")
} else if x == 10 {
print("Equal")
} else {
print("Small")
}
Match 语句
替代bash的switch语句。
let x = "a"
match x {
"b" => echo "is letter",
_ => echo "is others"
}
循环语句
repeat 循环
repeat 10 {a += 1} # 输出[1,2...10]
For 循环
for i in 0..5 { # 输出 0,1,2,3,4
print(i)
}
for i in [1,5,8] { print i }
for i in *.md { #支持*扩展
fs.cp i /tmp/
}
支持*扩展
While 循环
let count = 0
while count < 3 {
print(count)
count = count + 1
}
Loop 循环
let count = 0
let result = loop { #语句也可以作为表达式使用
print(count)
count = count + 1
if count > 3 {
break count
}
}
print result
语句表达式
控制语句也可以作为表达式使用:
let a = if b>0 {5} else {-5}
条件表达式
a = c>0 ? t : f
支持嵌套。
六、函数
fn函数定义
- 使用
fn
定义,支持默认参数,支持剩余参数收集。fn add(a,b,c=10,*d) {
return a + b + c + len(c)
}
# 等价于:
fn add(a,b,c=10,*d) {
a + b + c + len(c)
}
echo add(2, 3) # 输出 5
echo add(2,3,4, 5) # 输出 10
lambda表达式
使用
->
定义。let add = (x,y) -> x + y
lambda 与 普通函数的区别:
- lambda 不支持默认参数和return语句,不支持剩余参数收集。
- lambda 支持部分参数应用,返回后续lambda
相同的是:
lambda和函数都 继承当前环境变量,在隔离的环境中运行,不会污染当前环境。