ion Shell 备忘录
版本:ion 1.0.5 alpha
Github:https://github.com/redox-os/ion
手册:https://doc.redox-os.org/ion-manual/
启动
# 通过指定下面的环境变量,可以显示命令的执行时长
RECORD_SUMMARY=1 ion
# 通过 -x 参数启动,可以在执行每条命令之前打印该命令
# 通过该方法,可以知道命令提示符是通过 PROMPT 命令打印出来的
ion -x
基本操作
# 初始化文件
$HOME/.config/ion/initrc
# 历史记录文件
$HOME/.local/share/ion/history
# 可以直接输入路径来切换目录,免输入 cd
/bin
# 可以使用 \ 折行,忽略 \ 之后的换行符和连续空白
echo hello \
world
# 可以在一行中写多条命令,用分号分隔
echo hello; echo world
# 修改提示符
fn PROMPT
echo -n "${USER}:${PWD}# "
end
# 命令行参数
echo @args
echo @args[0]
IO 重定向
#!/usr/bin/ion
echo hello > a.txt # 将标准输出写入到文件
echo world >> a.txt # 将标准输出追加到文件
cat < a.txt # 读取文件
ls --err ^> a.txt # 将标准错误写入到文件
grep --err ^>> a.txt # 将标准错误追加到文件
cat < a.txt # 读取文件
ls &> a.txt # 将标准输出和标准错误都写入到文件
ls --err &>> a.txt # 将标准输出和标准错误都追加到文件
cat < a.txt # 读取文件
cat <<< "hello world" # 读取字符串
cat <<< $(echo hello) # 读取命令的输出
echo hello > a.txt > b.txt # 多重重定向
cat < a.txt < b.txt # 多重重定向
# 无法在 $() 中的函数上使用重定向
fn f; echo hello; end
echo $(f > /dev/null) # hello
管道
#!/usr/bin/ion
ls | wc -l # 标准输出管道
ls --err ^| wc -l # 标准错误管道
ls *a* b &| wc -l # 标准输出和标准错误管道
# 无法在 $() 中的函数上使用管道
fn f; echo hello; end
echo $(f | wc -l) # hello 0
后台执行
SIGINT (Ctrl+C) # 用终止信号中断正在运行的当前作业
SIGTSTP (Ctrl+Z) # 将停止信号暂停正在运行的作业并发送到后台
当使用 Ctrl+Z 停止前台任务时,该任务将被暂停并添加到后台进程列表。当执行命令时以 & 结尾时,该任务将在后台继续运行。要恢复已暂停的任务,可以执行 bg <job_id>
命令,它将发送一个 SIGCONT
信号到指定的 job_id,然后该任务恢复工作。fg
命令会做同样的事情,同时它会将该任务从后台带到前台。如果没有给 bg
或 fg
提供参数,则将使用上一个作业作为输入。
# 后台执行
cmd &
# 后台脱离 Shell 执行
cmd &!
# 列出所有后台运行的作业
jobs
# 将指定的作业发送到后台(如果它处于停止状态则唤醒)
bg jobid1 jobid2 ...
# 将指定的作业发送到前台(如果它处于停止状态则唤醒)
fg jobid1 jobid2 ...
# 从后台进程列表中移除指定的作业
disown jobid1 jobid2 ...
# 从后台进程列表中移除所有正在运行的作业
disown -r
# 如果没有提供 jobid,则从后台进程列表中移除所有作业
disown -a jobid
# 当 Shell 接收到 SIGHUP 时,所有作业将不接收 SIGHUP 信号
disown -h
# 当 Shell 接收到 SIGHUP 时,所有作业将接收 SIGHUP 信号
disown +h
# 当 Shell 接收到 SIGHUP 时,指定的作业将不接收 SIGHUP 信号
disown -h jobid1 jobid2 ...
# 当 Shell 接收到 SIGHUP 时,指定的作业将接收 SIGHUP 信号
disown +h jobid1 jobid2 ...
# 等待一个后台进程结束
wait
# 退出 Shell 时不结束后台进程
huponexit
# 使用 SIGTSTOP 信号将当前 Shell 挂起
suspend
# 可以使用下面的命令查看被挂起的 ion 进程 ID
ps aux | grep " ion"
# 然后使用下面的命令恢复 ion 的运行
kill -SIGCONT ion_pid
历史记录
可以通过环境变量设置历史记录的行为:
-
HISTFILE:应保存历史记录的文件。在 Ion 启动时,将从这个文件中读取历史记录,当 ion 退出时,会话的历史将被附加到这个文件中。默认值:$HOME/.local/share/ion/history
-
HISTFILE_ENABLED:是否应该从指定的文件 HISTFILE 中读/写历史记录。默认值:1。1 表示是,其他表示不是。
-
HISTFILE_SIZE:从内存中刷新时保留在历史文件中的最大行数。默认值:100000。将其设置为高于 HISTORY_SIZE 的值是没有用的。理想情况下,这些变量应该具有相同的值,否则会导致写入磁盘的历史信息丢失,考虑到现在廉价的硬件空间,这可能不值得。(目前被忽略)
-
HISTORY_IGNORE:用于设置哪些命令不应保存在历史记录中。默认值:[ no_such_command whitespace duplicates ]。数组的每个元素都可以采用以下选项之一:
- all:所有命令都被忽略,历史记录中不会保存任何内容。
- no_such_command:返回 NO_SUCH_COMMAND 的命令不会保存在历史记录中。
- whitespace:空格字符开头的命令将不会保存在历史记录中。
- regex:xxx:其中 xxx 被视为正则表达式。匹配此正则表达式的命令将不会保存在历史记录中。
- duplicates:输入匹配的命令后,所有前面的重复命令都会从历史记录中删除/忽略。
-
HISTORY_SIZE:内存中历史记录包含的最大行数。默认值:1000。理想情况下,该值应与 HISTFILE_SIZE 相同。(目前被忽略)
-
HISTORY_TIMESTAMP:是否应与每个命令一起记录相应的时间戳。时间戳用一个未格式化的自 unix 纪元以来的秒数。默认值:0。0 表示禁用,1 表示启用。
赋值
#!/usr/bin/ion
let a = hello
let a ?= 1 # 如果 a 存在,则不赋值,否则创建并赋值
let a b = 1 2
let a b = $b $a
echo $a $b # 2 1
# 支持类型约束
let a:bool = true
let a:int = 32
let a:float = 3.2
let a:str = 3.2 # 正确,相当于 let a = 3.2
let a:bool = 32 # 失败,类型不匹配
echo $a # 3.2
# 数组类型
let a:[str] = [ hello world ]
let a:[int] = [ 1 2 3 [ 4 5 ] ]
echo @a # [ 1 2 3 4 5 ]
# 映射 hmap 和 bmap,[]用于指定值类型,键类型只能是 str
# 元素的等号前后不能有空格
# 无序映射(快)
let a:hmap[int] = [ a=1 c=3 b=2 ]
echo @a # c 3 a 1 b 2 # 顺序不固定
# 有序映射(慢)
let a:bmap[int] = [ a=1 c=2 b=3 ]
echo @a # a 1 b 3 c 2
# 存放数组的映射
let a:bmap[[int]] = [ a=[1 2] b=[3 4] ]
let a[c] = [5 6] # 追加元素
echo @a # a 1 2 b 3 4 c 4
# 不需要的变量可以删除
drop a
数学运算
#!/usr/bin/ion
# 赋值运算(+= -= *= /= //= **=)
let a = 3
let a /= 2 # 小数除法
echo $a # 1.5
let a = 3
let a //= 2 # 整数除法
echo $a # 1
let a b = 3 3.2
let a b **= 2 2 # 乘方
echo $a $b # 9.0 10.240000000000002
# 数学运算(+ - * / % ** & | ^ ~ << >> ² ³)
# ² ³ 只能用于 math 函数
echo $((3 ** 2)) # 4
echo $(math 3 ** 2) # 3.4
字符串
字符串变量通过 $ 引用
#!/usr/bin/ion
let a = hello
echo "$a" # hello 展开变量
echo '$a' # $a 不展开变量
# 字符串可跨行(目前只在脚本中可用)
echo "hello
world"
let s = "hello string"
echo $s # hello string
let s = 你好
echo $s[1] # 好
# 字符串切片
let s = "Hello world"
echo $s[3..8] # lo wo
echo $s[3..=8] # lo wor
echo $s[3...8] # lo wor
echo $s[3..-1] # lo worl
echo $s[3..=-2] # lo worl
echo $s[3...-2] # lo worl
echo $s[3..] # lo world
echo $s[..8] # Hello wo
echo $s[..] # Hello world
# 字符串连接
let a = aaa
let b = bbb
let c = $a$b
echo $c # aaabbb
let a ++= $b
echo $a # aaabbb
let a ::= $b
echo $a # bbbaaabbb
# 大括号扩展
let s = job_{01,02}.{ext1,ext2}
echo $s # job_01.ext1 job_01.ext2 job_02.ext1 job_02.ext2
let s = hello\ { world, array}
echo $s # hello world hello array
echo {1..10} # 1 2 3 4 5 6 7 8 9
echo {d...a} # d c b a
# 从命令输出创建字符串
let s = $(seq 5)
echo $s # 1\n2\n3\n4\n5
echo $(seq 5)[3..] # \n3\n4\n5
# 字符串转数组
let s = "1 2 3 4 5"
let a = [ @split(s) ]
echo @a[1] # 2
字符串方法
左括号与参数之间不能有空格
#!/usr/bin/ion
echo $basename("/parent/filename.ext") # filename.ext
echo $extension("/parent/filename.ext") # ext
echo $filename("/parent/filename.ext") # filename
echo $parent("/parent/filename.ext") # /parent
echo $join([ 1 2 3 ]) # 1 2 3
echo $join([ 1 2 3 ] ", ") # 1,2,3
echo $find("abcde" "cd") # 2
echo $len("你好") # 2
echo $len([ 1 2 3 ]) # 3
echo $len_bytes("你好") # 6
echo $len_bytes([ 1 2 3 ]) # 5
echo $replace("abcabc" ab z) # zczc
echo $replacen("abcabc" ab z 1) # zcabc
echo $regex_replace("aba" "^a" "A") # Aba
echo $repeat("abc" 3) # abcabcabc
echo $reverse("abc") # cba
echo $to_lowercase("aBc") # abc
echo $to_uppercase("aBc") # ABC
echo $escape("can't") # can\'t
echo $unescape("can\'t") # can't
# 如果变量不存在或为空字符串,则返回默认值
echo $or($some_var "default value") # default value
let some_var = ""
echo $or($some_var "default value") # default value
let some_var = 0
echo $or($some_var "default value") # 0
数组
数组变量通过 @ 引用
#!/usr/bin/ion
let a = [ 1 2 3 4 5 true abc ]
echo @a # 1 2 3 4 5 true abc
echo @a[0] # 1
echo @a[1] # 2
let a[5] = 9
echo @a # 9 2 3 4 5 9 abc
let a[9] = 9 # 忽略越界的索引
echo @a # 9 2 3 4 5 9 abc
# 数组切片
echo @a[1..3] # 2 3
# 任意选择元素
echo @a[0..2 4 2..5 0] # 1 2 5 3 4 5 1
# 范围数组
echo [{1..6}] # 1 2 3 4 5
# 步进范围数组
echo [{1..2..10}] # 1 3 5 7 9
# 逆序步进范围数组
echo [{z..3..a}] # z w t q n k h e b
echo [{10..-3..-10}] # 10 7 4 1 -2 -5 -8
# 扩展数组
echo [a{a,b,c} d] # aa ab ac d
# 复制数组
let a = [ 1 2 3 ]
let b = [ @a ]
# 连接数组
let a = [ 1 2 ]
let b = [ 3 4 ]
let c = [ @a @b 5 ]
echo @c # 1 2 3 4 5
let a ++= @b
echo @a # 1 2 3 4
let a ::= 5
echo @a # 5 1 2 3 4
# 剔除元素
let a = [ 1 1 2 2 2 3 4 5 5 ]
let a \\= [ 2 3 ]
echo @a # 1 1 4 5 5
let a \\= 1
echo @a # 4 5 5
# 通过执行命令创建数组
let a = [ @(seq 5) ]
echo @a # 1 2 3 4 5
echo @(seq 5)[3..] # 4 5
# 数组转字符串
let a = [ 1 2 3 ]
let s = @a
echo $s # 1 2 3
数组方法
左括号与参数之间不能有空格
#!/usr/bin/ion
# 以 \n 切分字符串
for i in @lines($unescape("aaa\nbbb\nccc\nddd"))
echo $i
end
# 以空白符切分字符串
for i in @split($unescape("aaa\nbbb ccc\tddd"))
echo $i
end
# 以指定子串切分字符串
for i in @split("abc,def,ghi" ",")
echo $i
end
# 在指定位置拆分字符串为两个子串
echo @split_at("HelloWorld" 5) # Hello World
# 字符串转字节数组
echo @bytes("你好") # 228 189 160 229 165 189
# 字符串转 Unicode 码点数组
# 这里的 ❤️ 是由 2 个 Unicode 码点组成的
echo @chars("你好❤️世界") # 你 好 ❤ ️ 世 界
# 字符串转字形数组
# 这里的 ❤️ 是由 2 个 Unicode 码点组成的
echo @graphemes("你好❤️世界") # 你 好 ❤️ 世 界
# 反转数组
echo @reverse([1 2 3]) # 3 2 1
彩色字符
ion Shell 内置了一些命名空间,可以访问特定的成员。
命名空间的格式:${namespace::variable}
#!/usr/bin/ion
echo ${c::red}red
echo ${c::green}green
echo ${c::blue}blue
echo ${c::light_red}light_red
echo ${c::light_green}light_green
echo ${c::light_blue}light_blue
echo ${c::cyan}cyan
echo ${c::magenta}magenta
echo ${c::yellow}yellow
echo ${c::light_cyan}light_cyan
echo ${c::light_magenta}light_magenta
echo ${c::light_yellow}light_yellow
echo ${c::dark_gray}dark_gray
echo ${c::light_gray}light_gray
echo ${c::black}black
echo ${c::default}default
echo ${c::black}${c::whitebg}bg # 颜色名后面加 bg 表示设置背景色
echo ${c::black,whitebg}bg${c::defaultbg} # 颜色名后面加 bg 表示设置背景色
echo ${c::blink}style${c::reset} # 闪烁
echo ${c::bold}style${c::reset} # 加粗
echo ${c::dim}style${c::reset} # 黯淡
echo ${c::hidden}style${c::reset} # 隐藏
echo ${c::reverse}style${c::reset} # 反色
echo ${c::underlined}style${c::reset} # 下划线
for i in {0...9,A...F}
eval echo \$\{c::0x$i\}0x$i
end
echo ${c::0xF00}0xF00
echo ${c::0x0F0}0x0F0
echo ${c::0x00F}0x00F
echo ${c::0xFF0000}0xFF0000
echo ${c::0x00FF00}0x00FF00
echo ${c::0x0000FF}0x0000FF
环境变量
#!/usr/bin/ion
# 导出变量到全局环境
export a = 123
# 读取环境变量
echo ${env::a}
if
# 如果 command 的返回状态 $? 为 0 则表示 true,其它表示 false
if command
...
else if command
...
else
...
end
# else 是一条单独的语句
if command; ...; else; ...; end
示例
#!/usr/bin/ion
fn some_func
return 5
end
if some_func
echo ok
else
echo fail
end
for
#!/usr/bin/ion
for i in 1...3
echo -n $i, # 1,2,3,
end
echo
for a b c in 1..10
echo $a $b $c
end
# 1 2 3
# 4 5 6
# 7 8 9
for i in {1...3} [ 0.5 true hello ] a{a,b,c} 100
echo -n $i, # 1,2,3,0.5,true,hello,aa,ab,ac,100,
end
echo
for i in @split("aaa,bbb,ccc" ",")
echo -n $i, # aaa,bbb,ccc,
end
echo
while
#!/usr/bin/ion
let i = 0
while test $i -lt 10
echo -n $i, # 0,1,2,3,4,5,6,7,8,9,
let i += 1
end
echo
let i = 0
while true
let i += 1
if test $((i % 2)) -eq 0
continue
end
if test $i -gt 10
break
end
echo -n $i, # 1,3,5,7,9,
end
echo
match
#!/usr/bin/ion
# 空字符串会匹配所有分支
let s = that
match $s
case this
echo this
case that
echo that
case _
echo else
end
# 多匹配
let s = these
match $s
case [ this these ]
echo this
case [ that those ]
echo that
case _
echo else
end
# 匹配守卫
let s = these
match $s
case [ this these ] if false
echo this
case [ that those ]
echo that
case _
echo else
end
函数
#!/usr/bin/ion
fn add x:int y:int
echo $((x + y))
end
let n = $(add 3 5)
echo $n
函数作用域
#!/usr/bin/ion
let x = 9 # 定义全局变量 x
# 函数只能读取全局变量,无法修改
fn print_vars -- 函数简短描述,可被 fn 打印出来
echo $x # 读取全局变量 2(由 if 语句修改)
let x = 4 # 定义局部变量,覆盖全局变量
echo $x # 读取局部变量 4
fn nested # 嵌套函数
let x = 5 # 定义局部变量,覆盖父级变量
echo ${super::x} # 读取上级变量 4
echo ${global::x} # 读取全局变量 2
echo $x # 读取局部变量 5
end
nested
echo $y # y 属于 if 语句的局部变量,无法读取
end
if test 1 == 1
let x = 2 # 更新全局变量 x
let y = 3 # 定义局部变量 y
print_vars
end
逻辑运算符
#!/usr/bin/ion
# 如果 test 返回成功(0),则执行 echo
test -d /etc/somefile && echo exists
# 如果 test 返回失败(非 0),则执行 echo
test -d /etc/somefile || echo not exists
# 可以代替简单的 if 语句
true || echo ok && echo fail
内置条件函数
如果函数的返回状态 $? 为 0 则表示执行成功,非 0 表示执行失败。
# 判断字符串 str 中是否包含任意一个 sub 子串
contains str sub sub ...
# 判断字符串 str 是否以任意一个 sub 子串开头
starts-with str sub sub ...
# 判断字符串 str 是否以任意一个 sub 子串结尾
ends-with str sub sub ...
# 判断字符串 str 是否为空
test -z str
# 判断字符串 str 是否不为空
test -n str
test str
# 判断字符串 str1 是否等于 str2
test str1 = str2
# 判断字符串 str1 是否不等于 str2
test str1 != str2
# 判断整数 int1 是否大于 int2
test int1 -gt int2
# 判断整数 int1 是否大于等于 int2
test int1 -ge int2
# 判断整数 int1 是否小于 int2
test int1 -lt int2
# 判断整数 int1 是否大于等于 int2
test int1 -le int2
# 判断整数 int1 是否等于 int2
test int1 -eq int2
# 判断整数 int1 是否不等于 int2
test int1 -ne int2
# 判断文件 file1 和 file2 是否拥有相同的设备号和 inode 号
test file1 -ef file2
# 判断文件 file1 是否比 file2 更新
test file1 -nt file2
# 判断文件 file1 是否比 file2 更旧
test file1 -ot file2
# 判断 file 是否存在
test -e file
# 判断 file 是否存在,并且不为空
test -s file
# 判断 file 是否存在,并且为目录
test -d file
# 判断 file 是否存在,并且为普通文件
test -f file
# 判断 file 是否存在,并且为符号链接
test -h file
test -L file
# 判断 file 是否存在,并且为块设备
test -b file
# 判断 file 是否存在,并且为字符设备
test -c file
# 判断 file 是否存在,并且为套接字
test -S file
# 判断 file 是否存在,并且有读取权限
test -r file
# 判断 file 是否存在,并且有写入权限
test -w file
# 判断 file 是否存在,并且有执行权限
test -x file
# 判断数组变量 var 是否存在,并且不为空
exists -a var
# 判断函数 func 是否存在
exists --fn func
# 判断可执行程序 file 是否存在于 PATH 路径中
exists -b file
# 判断字符串 str 是否不为空,相当于 test -n
exists -s str
exists str
# 判断目录 file 是否存在,相当于 test -d
exists -d file
# 判断文件 file 是否存在,相当于 test -f
exists -f file
# 判断当前标准输入是否为 tty
isatty
# 判断文件描述符 fd 是否为 tty
isatty fd
# 判断 str 是否匹配正则表达式 reg
matches str reg
# 判断 arg1 和 arg2 是否相同,相当于 ==
is arg1 arg2
eq arg1 arg2
# 判断 arg1 和 arg2 是否不同,相当于 !=
is not arg1 arg2
eq not arg1 arg2
# 判断 var 是否为 1 或 true
bool var
# 什么都不做,只返回状态 0
true
# 什么都不做,只返回状态 1
false
and
or
not
其它内置函数
# 列出所有内建命令
help
# 获取指定内建命令的简短帮助
help cmd
# 获取指定内建命令的详细帮助
cmd -h
# 以退出码 0 退出 Shell
exit
# 以指定的退出码退出 Shell
exit n
# 显示历史记录
history
# 从标准输入读取一行字符串到 var1,再读一行到 var2,等等
read var1 var2 ...
# 丢弃指定的变量
drop var1 var2 ...
# 进入 dir 目录
cd dir
# 将当前目录入栈,并切换到 dir 目录
pushd dir
# 弹出栈顶目录并进入该目录
popd
# 打印当前入栈的目录列表
dirs
# 设置目录栈深度限制
dir_depth num
# 清除目录栈深度限制
dir_depth
# 在 PATH 中定位文件,同时搜索别名、自定义函数、内建函数
type name
which name
# 显示当前登录信息
status
# 判断当前 Shell 是否为一个登录 Shell
status -l
status --is-login
# 判断当前 Shell 是否为交互式 Shell
status -i
status --is-interactive
# 显示当前运行的脚本名,如果不是脚本,则显示 stdio
status -f
status --current-filename
# 输出各个参数,以空格分隔
echo str1 str2 ...
# 输出各个参数,不添加换行符
echo -n str1 str2 ...
# 输出各个参数,处理转义字符
echo -e str1 str2 ...
# 输出各个参数,不添加空格分隔符,紧密排列
echo -s str1 str2 ...
# 格式化输出(参考 C 语言的语法)
printf "%s %d %+3.2f" 1 1 1
# 定义别名
alias ls = ls --color
# 取消别名
unalias ls
# 列出所有自定义函数
fn
# 支持浮点数的计算器
math "3 * 4 + 5"
math "+ * 3 4 5"
# 获取一个 [0, 32767] 之间的随机数
random
# 获取一个 [min, max] 之间的随机数
random min max
# 将 str 当作命令执行
eval str
# 执行程序并替换当前 Shell
exec cmd args
# 使用空环境变量执行程序并替代当前 Shell
exec -c cmd args
# 执行指定文件中的命令,其中的命令可以修改当前环境变量
source file
# 执行参数中给出的脚本,并将 env vars diff 应用于当前 shell
# 如果脚本是文件,则执行该文件,否则将其视为脚本字面量
source-sh
# 开启调试
debug on
# 关闭调试
debug off
# 如果所执行命令的退出状态不为 0,则退出 Shell
set -e
# 如果所执行命令的退出状态不为 0,则不退出 Shell
set +e
keybindings
示例
#!/usr/bin/ion
# ============================================================
# 该脚本列出当前目录下的所有 .rs 文件供用户选择
# 然后调用 rustc 进行编译,然后执行
# ============================================================
# 帮助信息
let help = "
请选择要编译的文件:
如果直接回车,则使用上次的选择
如果输入 'r编号' 可以只运行,不编译(run)
如果输入 'c编号' 可以只编译,不运行(compile)
如果输入 'd' 可以删除编译结果(delete)
如果输入 '-' 可以指定编译选项,比如 '--help'
如果只输入单独的 '-' 字符,可以清除编译选项
如果只输入单独的 '-' 字符,可以关闭提示信息
"
# 如果带参数启动,则不显示帮助信息
if test $len(@args) -gt 1
let help = ""
end
let last = " " # 用于记录用户上次输入的内容
let args = "" # 用于记录用户设置的编译选项
# 循环显示,直到用户输入 q 并回车
while true
# 记录当前目录下的 *.rs 文件列表
let files = [ *.rs ]
# 显示帮助信息
echo -n $help
# 列出文件,供用户选择
echo
let i = 1
for file in @files
echo " $i - $file"
let i += 1
end
echo " q - 退出"
echo
echo "[ 上次选择:$last ]"
# 等待用户输入
read 选择
# 清屏
clear
# 加下划线,避免用户输入的 --help 被 test 当成自己的参数去解析
if test _$选择 = _
# 如果输入为空,则使用上次输入的内容
let 选择 = $last
else
# 如果输入不为空,则保存输入的内容
let last = $选择
end
# 解析动作和编号
let act = $选择[0]
let num = $选择
# 如果输入 q 则退出循环
if test $act = q
break
# 如果输入 r 则只运行,不编译
# 如果输入 c 则只编译,不运行
else if test $act = r || test $act = c
# 如果没有指定编号,则重新输入
if test $len($选择) -eq 1
continue
end
let num = $选择[1..]
# 如果输入 - 开头的字符串则指定编译选项
else if test $act = -
let args = $选择
# 如果只输入 - 则清除编译选项和提示信息
if test _$args = _-
let args = ""
let help = ""
end
continue
# 删除编译结果
else if test $act = d
# 如果没有文件可删除,则不执行任何操作
if ! exists -a files
echo ------------------------------
echo ${c::0xFF0}没有文件可删除${c::reset}
echo ------------------------------
continue
end
# 用来存储要删除的文件
let removes = []
# 获取即将被删除的文件的列表
for file in @files
# 去掉文件扩展名
let file = $filename($file)
# 判断文件是否存在
if test -f $file
# 添加到文件列表
let removes ++= $file
end
end
# 如果没有文件可删除,则不执行任何操作
if ! exists -a removes
echo ------------------------------
echo ${c::0xFF0}没有文件可删除${c::reset}
echo ------------------------------
continue
end
clear
# 列出即将被删除的文件的列表
echo
for file in @removes
echo " - $file"
end
# 删除前需要用户确认
echo
echo '是否删除上述文件 [y/N]?'
read 是否删除
echo
clear
echo ------------------------------
if test _$是否删除 = _y || test _$是否删除 = _Y
# 执行删除操作
for file in @removes
rm -f $file
end
echo ${c::0x0F0}删除完毕!${c::reset}
else
echo ${c::0xFF0}取消删除!${c::reset}
end
echo ------------------------------
continue
end
# 检查输入的编号是否为数字
match $num
case {1..100}
# 数组下标从 0 开始计数
let num -= 1
# 检查是否超出数组范围
if test $num -lt $len(@files)
# 获取选择的文件名
let file = @files[$num]
echo ------------------------------
# 编译
if test $act != r
# 如果直接写 rutc $file $args
# 则会把 $args 整体当作一个参数传入
eval "rustc $file $args"
end
# 运行
if test $act == c
echo ${c::0x0F0}编译完毕!${c::reset}
else
./$filename($file)
end
echo ------------------------------
end
end
end
已知 Bug
1、当子 Shell 返回的数据超过 65535 时会被阻塞
echo $repeat("a" 65535) > a.txt
echo $repeat("b" 65536) > b.txt
echo $(cat a.txt) # ok
echo $(cat b.txt) # fail
cat b.txt | cat # ok
2、子 Shell 中函数体重定向错误
fn foo
echo hello
return 100
end
let a = $(foo > /dev/null; echo $?)
echo $a # want "100" but "hello 100"
foo > /dev/null # ok
echo $? # ok