xonsh 是 python 驱动的 shell, 在操作效率, 交互和外部功能的先进性上比 bash 等优秀, 并且十分容易上手. 但相应地, 它是一个新兴的 shell, 并且不是所谓 “POSIX Shell”(尽管某些行为比较相似), 所以仍需做一些了解.
xonsh 的提示符为 @, 因为它读作 “consh”, 脚本文件扩展名为 .xsh, 官方定义的 markdown 代码块标签也是 xsh
截至本文最后更新, xonsh 的最高版本是 0.22.8
特殊语法
Shell 与 Python 的集成是 xonsh 易用性的根源, xonsh 向下直接兼容多数 python 脚本语法, 并且有提供独特语法以便将 python 和 shell 集成
命令解释与机制相关
引用和处理字符串变量
在 xonsh 中, 你可以直接使用 print(x) 输出一个变量和对象, 但若想将它作为 touch 命令的参数呢? xonsh 能带你告别 bash 丑陋的字符串拼接和 for 语法.
xonsh 使用 @() 操作符在 shell 环境中引用一个变量, 它会自动对字符串对象进行 str() 而不是 repr() 操作(也即直接追加字符串), 可以通过下列代码验证:
(注意, 对于其他类型 @() 还有其他优雅的行为, 详见下文)
tmp @ text = "Hello\nWorld"
tmp @ repr(text)
"'Hello\\nWorld'"
tmp @ str(text)
'Hello\nWorld'
tmp @ touch @(text)
tmp @ ls
'Hello'$'\n''World' # 文件名中真的有换行
tmp @ # 如果是 repr(), 就会创建 'Hello\nWorld' (无转义)
因此你可以这样创建一组数字序号文件
tmp @ for i in range(30):
touch @(i)
tmp @ ls
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29
tmp @
或者方便地补0
tmp @ for i in range(30):
touch @(str(i).zfill(2))
tmp @ ls
00 02 04 06 08 10 12 14 16 18 20 22 24 26 28
01 03 05 07 09 11 13 15 17 19 21 23 25 27 29
同样的简单操作若用 bash 进行, 需要:
for i in {0..29}; do touch $(printf "%02d" $i); done
或
# 避免命令替换的子 shell 开销
for i in {0..29}; do
printf -v name "%02d" "$i"
touch "$name"
done
python 还有 bash 无法提供的一系列支持库与工具, 可处理多种格式文件而无需引用外部程序; 并且处理含有奇葩字符的文件名, xonsh 可以使用字符串的 repr 集成到命令中很难炸掉
不过, 同样的操作若用纯 python 脚本/REPL进行, 需要:
from pathlib import Path
for i in range(0, 30):
name = str(i).zfill(2)
path = Path(name)
path.touch()
这两种对比足以显示 xonsh 的优越性
别名与 aliases
在 xonsh 中, 别名被存储在全局的 aliases — 一个兼容字典 API 的 xonsh.aliases.Aliases 对象中.
像这样设置一个别名:
~ @ aliases['la'] = 'ls -a'
字符串形式建立的别名将自动转换为列表, 也即对象的智能性:
~ @ aliases['la'] = 'ls -a'
~ @ aliases['la']
['ls', '-a']
若要取消一个别名, 只需:
~ @ del aliases['la']
~ @ la
xonsh: subprocess mode: command not found: 'la'
此外, 扩展 xontrib-abbrevs (需要安装 xontrib-abbrevs 包) 还提供了一套独立的, 输入后会自动展开的类 alias 系统, 存放于全局的 abbrevs 字典对象中
~ @ abbrevs['ll'] = 'ls -l'
~ @ ls -l # 输入 ll 回车或空格后自动补全
总计 36
drwxr-x--- 4 pluv wheel 4096 4月11日 12:25 Desktop
drwxr-x--- 10 pluv wheel 4096 4月 8日 00:18 Documents
drwxr-x--- 5 pluv wheel 4096 4月12日 09:03 Downloads
drwxr-x--- 2 pluv wheel 4096 2月 6日 15:18 Music
drwxr-x--- 5 pluv wheel 4096 3月26日 09:04 Pictures
drwxr-x--- 10 pluv wheel 4096 4月 9日 19:39 Programs
drwxr-x--- 2 pluv wheel 4096 3月10日 21:58 Public
drwxr-x--- 2 pluv wheel 4096 2月 6日 15:18 Templates
drwxr-x--- 3 pluv wheel 4096 2月18日 21:18 Videos
~ @
路径匹配, 正则表达式和反引号
xonsh 和大多数 shell 一样, 默认使用 glob 匹配路径, 例如:
~ @ ls
Desktop Documents Downloads Music Pictures Programs Public Templates Videos
~ @ echo D*
Desktop Documents Downloads
~ @
在 xonsh 中, `` 不像在bash中一样代表进程替换. 考虑到反引号本身在 python3 中已经失去了作用(这玩意在 python2 中曾用于表示 repr), 于是 xonsh 的开发者十分聪明地给反引号分配了正则路径匹配功能, 无需用户去导入并繁琐地使用 re 和 pathlib 模块
这样你就可以用反引号括住正则表达式以对路径进行正则匹配
例如:
~ @ touch report2023.txt
~ @ ls `report.*\.txt`
report2023.txt
同理:
# 匹配 /etc 目录下所有 .conf 结尾的文件
ls `/etc/.*\.conf`
# 匹配 /tmp 目录下任意深度的 .log 文件
ls `/tmp/.*\.log`
# 匹配家目录下所有 .py 文件
ls `~/.*\.py`
可以用这个程序探索匹配功能是怎么运作的
#include <iostream>
int main(int argc, char* argv[]) {
if (argc <= 1) {
std::cout << "没有提供任何参数" << std::endl;
return 0;
}
std::cout << "共 " << argc - 1 << " 个参数" << std::endl; // 文件名本身占一个 argv
for (int i = 1; i < argc; i++) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
return 0;
}
如下:
~ @ ls
Desktop Downloads Pictures Public Videos test.cc
Documents Music Programs Templates a.out
~ @ ./a.out `^D.*$` # 这个正则表达式匹配所有 D 开头的串
共 3 个参数
argv[1] = Desktop
argv[2] = Documents
argv[3] = Downloads
~ @ ./a.out `^D.*$` Music
共 4 个参数
argv[1] = Desktop
argv[2] = Documents
argv[3] = Downloads
argv[4] = Music
~ @ ./a.out Vid* `^D.*$` Musi*
共 5 个参数
argv[1] = Videos
argv[2] = Desktop
argv[3] = Documents
argv[4] = Downloads
argv[5] = Music
~ @
可见, 不同使用 glob, 正则匹配到的串都按照顺序优雅的串联起来成为命令的参数!
你可能想知道原理是什么, 这将在下一节介绍
再认识 @() 与类型解析系统
在上文我们提到, `` 包裹的内容可以对路径进行正则匹配, 但在 xonsh 眼中, `` 只是一个操作符, 这意味着它也是一个对象:
~ @ `^D.*$`
['Desktop', 'Documents', 'Downloads']
~ @ ``
[]
你可以像在 python 中一样调用 type() 函数查看对象类型, 最终发现 `` 实际上被优雅地解析为一个列表
~ @ type(``)
list
~ @
你还能直接给命令传递一个列表, 但注意要用 @() 包裹, 列表内容会按顺序作为命令的参数
~ @ ./a.out @(['0', '2', '1'])
共 3 个参数
argv[1] = 0
argv[2] = 2
argv[3] = 1
~ @
由此引出 xonsh 的对象化类型解析系统
实际上, xonsh 并不通过 “list” 这个类型名处理列表展开, 而是对具有 __iter__ 属性的对象展开
验证如下:
~ @ class CountDown:
"""倒计时可迭代对象, 这不是列表哦"""
def __init__(self, start):
self.start = start
def __iter__(self):
current = self.start
while current > 0:
yield current
current -= 1
~ @ ./a.out @(CountDown(10))
共 10 个参数
argv[1] = 10
argv[2] = 9
argv[3] = 8
argv[4] = 7
argv[5] = 6
argv[6] = 5
argv[7] = 4
argv[8] = 3
argv[9] = 2
argv[10] = 1
~ @
由此可知, 对于 iterable (意为 “可迭代”, 即具有 __iter__ 方法的)对象, xonsh 会在命令行中将其展开, 既不是 repr 也不是 str, 而是空格分隔的字符串.
现在你可能会想当然认为:
~ @ ./a.out @({'cat':'tom', 'mouse':'jerry', 'dog':'spark'})
的行为等同于
~ @ ./a.out --cat tom --mouse jerry --dog spark
然而现实很骨感
~ @ ./a.out @({'cat':'tom', 'mouse':'jerry', 'dog':'spark'})
共 3 个参数
argv[1] = cat
argv[2] = mouse
argv[3] = dog
~ @ ./a.out --cat tom --mouse jerry --dog spark
共 6 个参数
argv[1] = --cat
argv[2] = tom
argv[3] = --mouse
argv[4] = jerry
argv[5] = --dog
argv[6] = spark
~ @
对于 @() 操作符的行为, 此处有一个严谨的定义:
在子进程模式(也就是 xonsh 的上下文)下, @() 运算符形式可以工作, 并且会执行任意的 Python 代码. 执行结果会被追加到子进程的命令列表中.
如果结果是字符串或字节类型, 则直接追加到参数列表中;
如果结果是 iterable, 则将其内容转换为字符串并按顺序追加到参数列表中;
如果结果出现在”第一个位置”且为一个 python 函数, 则该函数会作为别名(ExecAlias 对象)运行.(见下文)
在其他情况下, 结果会自动转换为字符串.
由于字典对象具有的 __iter__ 是对 keys 的迭代, 所以才有如上的结果
使用函数
上文我们提到, 如果 @() 的结果出现在”第一个位置”且为一个 python 函数, 则该函数会作为别名(ExecAlias 对象)运行, 这也即是 xonsh 集成函数的方式, 让你可以像使用命令行工具一样使用函数.
这里的”第一个位置”并非”第一个参数”, 而是”xonsh 收到的参数列表”, 举个例子:
./a.out 1 中, a.out 是 “第一个位置”
xonsh 的函数集成是通过 ExecAlias 对象实现的, 因为 xonsh 将内置命令看作一个 “可执行别名”, 我们无需了解底层原理, 只需一个例子即可:
~ @ def say_meow(args):
num = int(args[0]) if args else 0
print(num * 'Meow')
~ @ @(say_meow) 3
MeowMeowMeow
~ @
在例子中 我们创建了一个 say_meow 函数, 需要注意的是, 能作为命令行使用的函数必须接受唯一的 args 参数, 它是一个以字符串存储命令行参数的列表
和 C 程序的 argv 不同的是, args[0] 存放的是第一个命令行参数(如上例中 3), 而不是 “函数/程序名”
你还可以使用别名系统注册这个函数到 aliases[‘cmd’] 中, 这样就可以无需 @() 像运行命令行工具一样运行函数
会话对象
xonsh 使用 @ 指代当前会话, 其为一个 XonshSessionInterface 对象, 具有以下公有属性:
@.env 是一个 xonsh.environ.Env 对象, 兼容字典 API, 用于存储或设置当前会话的环境变量
@.history 是一个 xonsh.history.json.JsonHistory 对象(当使用 JSON 后端时), 用于存储历史记录, 兼容列表 API
@.imp 是一个 xonsh.built_ins.InlineImporter 包导入器对象, 用于导入包
例如:
@ json = @.imp.json # 实际上等同于 import json
@.lastcmd 是一个 xonsh.procs.pipelines.HiddenCommandPipeline 或 xonsh.procs.pipelines.CommandPipeline 对象, 存储上一个命令的捕捉结果(无论显式或隐式捕捉, 具体见下文)
子进程相关
shell 本质上是人类友好且带命令解释功能的子进程与内建命令启动工具, xonsh 对 subprocess 的处理和其他 shell 不同
发起子进程而并不进行捕获
为了避免歧义, xonsh 实际上对任何子进程都是捕获的(包括不外包操作符地运行命令, 使用 $[], ![], $(), !()), 此节只是介绍了一种 xonsh 不提供任何捕获后信息的子进程发起方法.
例如, 即使你不外套操作符运行一个命令, 命令本身也会被隐式捕获, xonsh 会读取捕获后的 HiddenCommandPipeline(隐藏式命令管线) 对象读取返回值, 并根据你的配置显示或不显示在你的下一个提示符, 并把那个对象放入 @.lastcmd 中, 详情请看下下节.
xonsh 提供了 $[] 操作符以显式发起一个子进程, 它不会捕获或隐藏式地捕获关于子进程的任何信息给任何程序乃至 xonsh 本身(返回一个 None 对象)
~ @ $[ls]
Desktop Documents Downloads Music Pictures Programs Public Templates Videos
~ @ $[echo yeah]
yeah
这通常被用于脚本中, 例如只需要调用 time 命令让用户读它的输出, 又怕它和 python 的 time 包重名的情境
![] 会自动对包含内容进行变量引用解析后套上引号, 本身也可以包含字符串, 但字符串内的内容将不会被解析
一个例子:
~ @ text = 1
~ @ $['ls @(text)']
xonsh: subprocess mode: command not found: 'ls @(text)'
~ @
~ @ $[ls @(text)]
ls: 无法访问 '1': 没有那个文件或目录
~ @
另一个例子, 说明即使不套字符串, $[] 内的内容也不会作为 python 表达式被解析
~ @ $[time]
time: missing program to run
Try 'time --help' for more information.
~ @ $[time.time()]
File "<stdin>", line 1
$[time.time()]
^^^^
SyntaxError: ('code: (',)
捕获子进程信息
xonsh 使用 $() 和 !() 操作符捕获子进程, 但它们不是等价的
$() 操作符用于仅捕获子进程的输出
例如:
tmp @ $(ls -l)
'总计 0\n-rw-r--r-- 1 pluv wheel 0 4月12日 11:57 iamafile\n'
值得注意的是 xonsh 通过变量 $XONSH_SUBPROC_OUTPUT_FORMAT 来决定捕获得到的数据结构类型
默认是 'stream_lines', 即将所有输出行捕获为一个单一字符串
可将其设置为 'list_lines', 将所有输出行分行捕获为一个列表
例如
~ @ $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines'
~ @ $(ls -l)
['总计 36',
'drwxr-x--- 4 pluv wheel 4096 4月11日 12:25 Desktop',
'drwxr-x--- 10 pluv wheel 4096 4月 8日 00:18 Documents',
'drwxr-x--- 5 pluv wheel 4096 4月12日 09:03 Downloads',
'drwxr-x--- 2 pluv wheel 4096 2月 6日 15:18 Music',
'drwxr-x--- 5 pluv wheel 4096 3月26日 09:04 Pictures',
'drwxr-x--- 10 pluv wheel 4096 4月 9日 19:39 Programs',
'drwxr-x--- 2 pluv wheel 4096 3月10日 21:58 Public',
'drwxr-x--- 2 pluv wheel 4096 2月 6日 15:18 Templates',
'drwxr-x--- 3 pluv wheel 4096 2月18日 21:18 Videos']
~ @
不要将 $XONSH_SUBPROC_OUTPUT_FORMAT 设置为 'list_lines' 和 'stream_lines' 之外的其他值, 否则捕获是不会工作的
~ @ $XONSH_SUBPROC_OUTPUT_FORMAT = 'list'
~ @ $(ls -l)
~ @ # 啥也没有
另外, $() 只会捕获 stdout 流的内容, 也就是说, 如果内容是以 stderr 流输出的, xonsh 不会捕获它, 这些内容将被输出(如下例)
以及若命令不会在 stdout 输出结果(如下例), 那么 xonsh 将视 $XONSH_SUBPROC_OUTPUT_FORMAT 来将空字符串 '' 或 空列表 [] 而不是 None 对象或者 [''] 这种东西反馈给变量作为回退结果 (应该是考虑到处理类型的一致性吧)
~ @ $XONSH_SUBPROC_OUTPUT_FORMAT = 'stream_lines'
~ @ $(ls 不存在的目录)
ls: 无法访问 '不存在的目录': 没有那个文件或目录 # -> 这是输出的错误信息, xonsh 没有捕获它
'' # -> 这是捕获到的内容(一个空字符串)
~ @ s = $(ls 不存在的目录)
ls: 无法访问 '不存在的目录': 没有那个文件或目录
~ @ s
'' # -> 这是一个空字符串
~ @ $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines'
~ @ s = $(ls 不存在的目录)
ls: 无法访问 '不存在的目录': 没有那个文件或目录
~ @ s
[]
对于有颜色的内容, xonsh 会捕获其控制码
可以用这个小程序测试
#include <iostream>
int main(){
std::cout << "我是 stdout 的内容" << std::endl;
std::cerr << "我是 stderr 的内容" << std::endl;
std::cout << "我可以被捕获" << std::endl;
std::cerr << "我不会被捕获, 而是被直接输出" << std::endl;
std::cout << "\e[31m我是红色的, 我可以被捕获\e[0m" << std::endl;
std::cerr << "\e[32m我是绿色的, 我不会被捕获\e[0m" << std::endl;
return 0;
}
~ @ s = $(./a.out)
我是 stderr 的内容
我不会被捕获, 而是被直接输出
我是绿色的, 我不会被捕获
~ @ s
'我是 stdout 的内容\n我可以被捕获\n\x1b[31m我是红色的, 我可以被捕获\x1b[0m\n'
~ @
!() 则更为高级, 它捕获关于命令的更多信息, 并将其放入一个 CommandPipeline (命令管线) 对象中, 使其能够捕获所有流的内容及子进程的元数据
这个对象定义在 xonsh.procs.pipelines 中
~ @ !(ls 一个不存在的目录)
CommandPipeline(
returncode=2,
pid=40466,
args=['ls', '一个不存在的目录'],
alias=['ls', '--color=auto', '-v', '一个不存在的目录'],
executed_cmd=['ls', '--color=auto', '-v', '一个不存在的目录'],
timestamps=[1775966865.5426445, 1775966865.5505455],
input='',
output=[],
errors="ls: 无法访问 '一个不存在的目录': 没有那个文件或目录\n"
)
~ @ !(ls $HOME)
CommandPipeline(
returncode=0,
pid=40483,
args=['ls', '/home/pluv'],
alias=['ls', '--color=auto', '-v', '/home/pluv'],
executed_cmd=['ls', '--color=auto', '-v', '/home/pluv'],
timestamps=[1775966873.558355, 1775966873.5660608],
input='',
output=['Desktop', 'Documents', 'Downloads', 'Music', 'Pictures', 'Programs', 'Public', 'Templates', 'Videos']
)
以下是一份常用的映射表, 由于 xonsh 不会显示所有对象属性(例如不会显示值为 None 的属性), 所以此表常见但不完整
完整的 CommandPipeline 对象手册见 xonsh.procs.pipelines – xonsh 0.22.8 documentation
CommandPipeline(
returncode(int) -> 进程返回值, 0 为正常值
pid(int) -> 进程 PID
args -> 命令及其参数
alias(list/FuncAlias) -> 进程使用的别名信息, 可能是解析后命令序列(对于子进程), 也可能是 FuncAlias 对象(对于内建命令别名), 如果没有使用别名, 此对象为 None
executed_cmd(list) -> 解析后的进程参数
timestamps(list[int, int]) -> 进程起始及终止 UNIX 时间戳
input(str) -> 进程接受的输入 (stdin)
output(str/list) -> 进程的标准输出 (stdout)
errors(str) -> 进程的错误流输出 (stderr)
)
CommandPipeline.output 的格式依然受 $XONSH_SUBPROC_OUTPUT_FORMAT 控制, error 不受控制, 为 stream_line 格式
隐藏的捕获方式
术语隐藏式捕获进程的意思是: 不捕获命令的输出, 就像命令未被捕获一般, 但通过一个 HiddenCommandPipeline(隐藏式命令管线) 对象获得其输出之外的数据
事实上, xonsh 提供了 ![] 用于隐藏式捕获进程, 它的原理被同样应用到不外包操作符的命令执行中, 只是后者对写独立脚本的人几乎无用 — 你不能将不外包操作符的命令的 HiddenCommandPipeline 对象装进自己的变量里, 因为它的捕获仅仅用于 xonsh 的返回值和历史记录处理, 以及 @.lastcmd
HiddenCommandPipeline 部分复用了 CommandPipeline 的方法与对象, 以下是一个例子:
~ @ cap = ![ls]
Desktop Documents Downloads Music Pictures Programs Public Templates Videos
~ @ cap
~ @ cap.__repr__()
''
~ @ cap.__repr__
<bound method HiddenCommandPipeline.__repr__ of >
~ @ cap.__str__()
''
~ @ cap.__str__
<bound method CommandPipeline.__str__ of >
~ @ cap.output # 空字符串
''
~ @ cap.errors # None
对这些操作的归纳呈现于一个表格:
| 类型 | 输出 | 返回值 | 备注 |
|---|---|---|---|
| cmd | 不捕获 | 隐藏命令管道 | 与 ![cmd] 相同 |
| ![cmd] | 不捕获 | 隐藏命令管道 | |
| $[cmd] | 不捕获 | 无 | |
| !(cmd) | 捕获 | 命令管道 | |
| $(cmd) | 捕获 | 字符串 | 返回标准输出 |
内建命令相关
你可能注意到上文对 FuncAlias 对象的提及, 它出现在”捕获内建命令”的 CommandPipeline 对象中
xonsh 的内建命令实际上是一个 FuncAlias 对象封装的函数对象
对于内建命令, 也可以通过 $() 与 !() 捕获, 但行为有不同, 以下是一些例子
~ @ !(echo hi,xonsh)
CommandPipeline(
returncode=0,
pid=72351,
args=['echo', 'hi,xonsh'],
executed_cmd=['echo', 'hi,xonsh'],
timestamps=[1775982843.3403664, 1775982843.3477223],
input='',
output='hi,xonsh'
)
~ @ !(cd ~)
CommandPipeline(
returncode=0,
args=['cd', '/home/pluv'],
alias=FuncAlias({'name': 'cd', 'func': 'cd', 'return_what': 'result'}),
executed_cmd=['/home/pluv'],
timestamps=[1775982585.2600465, 1775982585.2614303],
input='',
output=''
)
可见 CommandPipeline 的 alias 对于内置命令为命令对应的 FuncAlias 对象, 因为 xonsh 认为内置命令调用是 FuncAlias 的别名
调优
.xonshrc, xontribs 和 xpip
主目录的 .xonshrc 文件是 xonsh 启动时自动执行的命令列表, 其使用 xonsh 的命令解析器
当然你也可以执行 xonsh --no-rc 来避免加载 .xonshrc 排错
xonsh 还跨平台提供 source-bash, source-zsh 和 source-cmd 来分别解析 bash, zsh 和 windows cmd.exe 命令解析器的运行命令文件, 例如使用 source-bash .bashrc 来迁移 bash 的配置文件
这些命令也有其他的 flag, 请参阅 xonsh 的文档
xontribs 是 xonsh 的插件系统
xontrib 命令可用于管理 xontrib
例如直接运行或运行 xontrib list 查看 xontrib 状态
~ @ xontrib
abbrevs loaded manual
argcomplete loaded manual
broot loaded manual
cd loaded auto
coreutils not-loaded
direnv not-loaded
dotdot loaded auto
free_cwd loaded manual
jedi loaded manual
sh loaded manual
uvox loaded auto
zoxide loaded manual
zoxide_init_cache not-loaded
执行 xontrib load 来加载指定的 xontrib
执行 xontrib reload 来重载指定的 xontrib
执行 xontrib unload 来取消加载指定的 xontrib
例如 xontrib load coreutils 会加载 xonsh 跨平台实现的一套 coreutils (也就是 ls 等命令组件), 它并不能提供”原生对象集成”, 性能和功能, 以及底层管理都不如 GNU coreutils 甚至 busybox 优秀, 唯二好处是调用开销较低和跨平台(对于 windows 来说比较方便)
xpip 是 xonsh 提供的 pip 别名, 它安装的包将仅安装至运行 xonsh 的 python 解释器包路径内
xpip 不是 pipx
可安装的 xontribs 可在 awesome-xontribs 仓库内找到(但这仓库有点老了, 虽然很多插件项目还活着)
以下是用 xpip 安装的比较有用的插件列表:
~ @ xpip install -U xontrib-abbrevs \
xontrib-argcomplete \
xontrib-free-cwd \
xontrib-zoxide \
xontrib-dotdot \
xonsh-direnv \
xontrib-sh \
xontrib-bashisms \
xontrib-uvox \
xontrib-broot \
xontrib-jedi \
xontrib-cd \
以下是说明:
- xontrib-cd: 让 cd 命令后路径不需要转义
- xontrib-abbrevs: 一套独立的 abbrevs 类 alias 系统, 好处是输入后会自动展开
- xontrib-jedi: 通过 jedi 语言服务器插件来实现 xonsh 的 python 补全, 需要安装 jedi LSP
- xontrib-broot: broot TUI 文件管理器集成(broot 不自带对 xonsh 的集成), 需要安装 broot, 默认配置
ctrl + N快捷键, 可以自动 cd 到选定目录, 对目录跳转比较有用 - xontrib-zoxide: 快速跳转目录工具, 个人认为没啥用
- xontrib-dotdot: 可以通过
......切换到上上上级目录 - xonsh-direnv: 自动加载目录下 .env 文件, 这个插件会破坏
@.lastcmd的行为, 因为 “上一条命令” 将永远是 direnv, 谨慎考虑安装 - xontrib-vox:
vox是 xonsh 的一套 python 虚拟环境系统, 这个插件将 xonsh 和vox集成 (没用过) - xontrib-uvox: 将 xonsh 和 python
uv包管理器像 vox 一样集成 (很有用) - xontrib-free-cwd: 不让 xonsh 占用工作目录以避免 windows 删除目录提示占用中和 umount 提示目标忙的问题
- xontrib-sh: 在 xonsh 内运行 bash, zsh, fish, tcsh, pwsh 命令, 原理依然是子进程, 但方便一些, 可以通过类似 IPython 的
! <命令>或者!bash <命令>快速使用, 效果等同于bash -c '<命令>' - xontrib-bashisms: 在 xonsh 内直接支持部分 bash 风格的命令, 例如
unset,export等 - xontrib-argcomplete: 用于支持 argparse 参数解析器, 来进行部分 python 和
.xsh脚本的参数补全, 它自己有一套参数生态, 自己写.xsh脚本运行时和对于一些 python 脚本很有用, 但对其他脚本来说无用
不要安装 xonsh-docker-tabcomplete, 这个插件实现老旧, 无法在高版本 docker 使用
xonfig web 的 xontrib 配置对于第三方 xontrib 有 bug, 最好不要用它, 因此要手动编辑 .xonshrc 来确保启用
以下是示例 .xonshrc 配置和一些有用的环境变量设置
# XONSH WEBCONFIG START
$PROMPT = '{BOLD_BLUE}{cwd_base}{RESET} @ ' # 提示符主题
$XONSH_COLOR_STYLE = 'default' # 颜色主题
$XONSH_SHOW_TRACEBACK = False # 不让 xonsh 显示 "是否启用 python traceback" 的提示
$SUGGEST_COMMANDS = False # 不让 xonsh 建议打错的命令
$XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines'
# xontrib load coreutils # 不加载 xonsh 的 coreutils
xontrib load jedi
xontrib load broot
xontrib load abbrevs
xontrib load argcomplete
xontrib load free_cwd
xontrib load zoxide
xontrib load direnv
xontrib load sh
# XONSH WEBCONFIG END
# 别名配置
aliases['l'] = 'ls'
aliases['la'] = 'ls -a'
aliases['ll'] = 'ls -l'
aliases['rrm'] = 'rm -rf'
aliases['cpr'] = 'rsync -ah --info=progress2'
aliases['cls'] = 'printf "\033c"'
aliases['lss'] = lambda: __import__('os').system('stat -c "%a %n" *')
source-bash $HOME/.profile
# 加载一些常用的 python 库以便调用
import math
import time as _time # 避免和系统 time 冲突
import os
import xonsh as _xonsh # 避免和 xonsh 冲突
import copy
import random
从 bash 转换与迁移
本节直接翻译自 Bash to Xonsh Translation Guide – xonsh 0.22.8 documentation, 提供 bash 中常见语法对应的 xonsh 等效写法
9, 10, 11 行中, 第一格内容实际上是第一二格内容(不知为啥博客园的解析器把 | 放到了表格内)
| Bash | Xonsh | 说明 |
|---|---|---|
| 没有表示会话的特殊对象 | @ |
@ 对象包含 @.env – 环境变量, @.imp – 导入器, @.lastcmd – 上一条命令等 |
script.sh |
script.xsh |
推荐的文件扩展名是 .xsh |
#!/bin/bash |
#!/usr/bin/env xonsh |
在 shebang 中使用 xonsh |
echo --arg="val" echo \; echo --arg "val" |
echo {} echo "{}" echo ";" |
请阅读子进程字符串教程以理解字符串如何成为 xonsh 中的参数. xonsh 中没有像 bash 中反斜杠 (\) 那样的转义字符概念. 可以使用单引号或双引号来移除某些字符、单词或括号的特殊含义. |
$NAME 或 ${NAME} |
$NAME |
按名称查找环境变量 |
export NAME=Peter |
$NAME = 'Peter' | 设置环境变量. 另请参阅 $UPDATE_OS_ENVIRON |
|
unset NAME |
del $NAME |
取消设置/删除环境变量 |
echo "$HOME/hello" | echo "$HOME/hello" |
使用环境变量构造参数 | |
echo $HOME/$(uname) |
echo @($HOME + '/' + $(uname)) |
连接变量或文本与运行命令的结果 |
echo 'my home is $HOME' | echo @("my home is $HOME") |
阻止环境变量展开 | |
${!VAR} | ${var or expr} |
通过另一个变量名查找环境变量. 在 xonsh 中, 这可以是任何有效表达式. | |
ENV1=VAL1 command |
$ENV1=VAL1 command 或 with @.env.swap(ENV1=VAL1): command |
设置临时环境变量并执行命令. 使用第二种带缩进块的表示法可在同一上下文中执行多条命令. |
alias ll='ls -la' |
aliases['ll'] = 'ls -la' |
xonsh 中的别名可以是作为字符串或参数列表的子进程命令, 也可以是任何 Python 函数. |
$(cmd args) 或 `cmd args` |
@$(cmd args) |
命令替换(允许命令的输出替换该命令本身). 将子进程命令的输出标记化并作为另一个子进程执行. |
v=`echo 1` v=$(echo 1) |
v=$(echo 1) |
在 bash 中, 反引号表示运行捕获的子进程——在 xonsh 中对应 $(). xonsh 中的反引号表示正则表达式 globbing(即 ls `/etc/pass.*`). |
echo -e "\033[0;31mRed text\033[0m" |
printx("{RED}Red text{RESET}") |
尽可能简单地打印彩色文本 |
shopt -s dotglob |
$DOTGLOB = True |
使用 * 或 ** 进行 globbing 时也会匹配点文件, 或那些名称以字面量 . 开头的“隐藏”文件. 与 bash 类似, 默认情况下此类文件会被过滤掉. |
if [ -f "$FILE" ]; |
p'/path/to/file'.exists() 或 pf'{file}'.exists() 或 if !(test -f $FILE): |
可以实例化 Path 对象并使用 p-string 语法直接检查 |
set -e |
$RAISE_SUBPROC_ERROR = True |
在非零返回码后引发失败. Xonsh 将引发 subprocess.CalledProcessError. |
set -x |
trace on 和 $XONSH_TRACE_SUBPROC = True |
在执行期间打开源代码行的跟踪 |
&& |
&& 或 and |
子进程的逻辑与运算符 |
|| |
|| 以及 or |
子进程的逻辑或运算符 |
$$ |
os.getpid() |
获取当前 shell 的 PID |
$? |
@.lastcmd.rtn |
返回上一条命令的退出码或状态. 要在 xonsh 脚本中获取命令的退出码, 对于非交互式进程请使用 !().rtn. |
$<n> |
$ARG<n> |
索引 n 处的命令行参数, 因此 $ARG1 相当于 $1 |
$@ |
$ARGS |
所有命令行参数和字符串的列表 |
while getopts |
使用 argparse 或 click |
另请参阅 awesome-cli-app 和 xontrib-argcomplete |
complete |
completer list |
与许多其他 shell 一样, xonsh 在按下 Tab 键时能够补全部分指定的参数. |
IFS |
$XONSH_SUBPROC_OUTPUT_FORMAT |
更改输出表示和拆分. 另请查看 DecoratorAlias 以获取返回对象的能力, 例如 j = $(@json echo '{}'). |
| 将补全显示为列表 | $COMPLETIONS_DISPLAY = 'readline' |
显示补全将模拟 readline 的行为 |
docker run -it bash |
docker run -it xonsh/xonsh:slim |
真的有人用容器只为跑一个 shell 吗? |
exit 1 |
exit 1 或 exit(1) |
退出当前脚本 |
本文完 :3
文章摘自:https://www.cnblogs.com/pluvium27/p/19856400
