ion Shell 备忘录

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 命令会做同样的事情,同时它会将该任务从后台带到前台。如果没有给 bgfg 提供参数,则将使用上一个作业作为输入。

# 后台执行
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

历史记录

可以通过环境变量设置历史记录的行为:

  1. HISTFILE:应保存历史记录的文件。在 Ion 启动时,将从这个文件中读取历史记录,当 ion 退出时,会话的历史将被附加到这个文件中。默认值:$HOME/.local/share/ion/history

  2. HISTFILE_ENABLED:是否应该从指定的文件 HISTFILE 中读/写历史记录。默认值:1。1 表示是,其他表示不是。

  3. HISTFILE_SIZE:从内存中刷新时保留在历史文件中的最大行数。默认值:100000。将其设置为高于 HISTORY_SIZE 的值是没有用的。理想情况下,这些变量应该具有相同的值,否则会导致写入磁盘的历史信息丢失,考虑到现在廉价的硬件空间,这可能不值得。(目前被忽略)

  4. HISTORY_IGNORE:用于设置哪些命令不应保存在历史记录中。默认值:[ no_such_command whitespace duplicates ]。数组的每个元素都可以采用以下选项之一:

  • all:所有命令都被忽略,历史记录中不会保存任何内容。
  • no_such_command:返回 NO_SUCH_COMMAND 的命令不会保存在历史记录中。
  • whitespace:空格字符开头的命令将不会保存在历史记录中。
  • regex:xxx:其中 xxx 被视为正则表达式。匹配此正则表达式的命令将不会保存在历史记录中。
  • duplicates:输入匹配的命令后,所有前面的重复命令都会从历史记录中删除/忽略。
  1. HISTORY_SIZE:内存中历史记录包含的最大行数。默认值:1000。理想情况下,该值应与 HISTFILE_SIZE 相同。(目前被忽略)

  2. 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值