内容简介:正文开始终端模拟器(通常简称为终端)就是一个“窗口”,是的,它运行一个基于文本的程序,默认情况下,它就是你登陆的 shell (也就是 Ubuntu 下的 bash)。当你在窗口中键入字符时,终端除了将这些字符发送到 shell (或其他程序)的 stdin 之外,还会在窗口中绘制这些字符。 shell 输出到 stdout 和 stderr 的字符被发送到终端,终端在窗口中绘制这些字符。
正文开始
- 这是一个使用 Rust 构建自己的 shell 的教程,已经被收录在 build-your-own-x 列表中。自己创建一个 shell 是理解 shell、终端模拟器、以及 OS 等协同工作的好办法。
shell 是什么?
- shell 是一个程序,它可以用于控制你的计算机。这在很大程度上简化了启动应用程序。但 shell 本身并不是一个交互式应用程序。
- 大多数用户通过终端模拟器来和 shell 交互。用户geirha 对终端模拟器的形容如下:
终端模拟器(通常简称为终端)就是一个“窗口”,是的,它运行一个基于文本的程序,默认情况下,它就是你登陆的 shell (也就是 Ubuntu 下的 bash)。当你在窗口中键入字符时,终端除了将这些字符发送到 shell (或其他程序)的 stdin 之外,还会在窗口中绘制这些字符。 shell 输出到 stdout 和 stderr 的字符被发送到终端,终端在窗口中绘制这些字符。
- 在本教程中,我们将编写自己的 shell ,并在普通的终端模拟器(通常在 cargo 运行的地方)中运行它。
从简单开始
-
最简单的 shell 只需要几行 Rust 代码。这里我们创建一个新字符串,用于保存用户输入。
stdin().read_line
将会在用户输入处阻塞,直到用户按下回车键,然后它将整个用户输入的内容(包括回车键的空行)写入字符串。使用input.trim()
删除换行符等空行,我们尝试在命令行中运行它。
fn main(){ let mut input = String::new(); stdin().read_line(&mut input).unwrap(); // read_line leaves a trailing newline, which trim removes let command = input.trim(); Command::new(command) .spawn() .unwrap(); } 复制代码
- 运行此操作后,你应该会在你的终端中看到一个正在等待输入的闪烁光标。尝试键入 ls 并回车,你将看到 ls 命令打印当前目录的内容,然后 shell 将推出。
- 注意:这个例子不能在Rust Playground 上运行,因为它目前不支持 stdin 等需要长时间等待的运行和处理。
接收多个命令
-
我们不希望在用户输入单个命令后退出 shell。支持多个命令主要是将上面的代码封装在一个
loop
中,并添加调用wait
来等待每个子命令的处理,以确保我们不会在当前处理完成之前,提示用户输入额外的信息。我还添加了几行来打印字符>
,以便用户更容易的将他的输入与处理命令过程中的输出区分开来。
fn main(){ loop { // use the `>` character as the prompt // need to explicitly flush this to ensure it prints before read_line print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let command = input.trim(); let mut child = Command::new(command) .spawn() .unwrap(); // don't accept another command until this one completes child.wait(); } } 复制代码
-
运行这段代码后,你将看到在运行第一个命令之后,会显示一个提示符,以便你可以输入第二个命令。使用
ls
和pwd
命令来尝试一下吧。
参数处理
-
如果你尝试在上面的 shell 上运行命令
ls -a
,它将会崩溃。因为它不知道怎么处理参数,它尝试运行一个名为ls -a
的命令,但正确的行为是使用参数-a
运行一个名为ls
的命令。 -
通过将用户输入拆分为空格字符,并将第一个空格之前的内容作为命令的名称(例如
ls
),而将第一个空格之后的内容作为参数传递给该命令(例如-a
),这个问题在下面就会解决。
fn main(){ loop { print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); // everything after the first whitespace character // is interpreted as args to the command let mut parts = input.trim().split_whitespace(); let command = parts.next().unwrap(); let args = parts; let mut child = Command::new(command) .args(args) .spawn() .unwrap(); child.wait(); } } 复制代码
shell 的内建功能
- 事实证明, shell 不能简单的将某些命令分派给另一个进程。这些都是影响 shell 内部,所以,必须由 shell 本身实现。
-
最常见的例子可能就是
cd
命令。要了解为什么 cd 必须是 shell 的内建功能,请查看这个链接。处理内建的命令,实际上是一个名为cd
的程序。这里有关于这种二象性的解释。 - 下面我们添加 shell 内建功能 cd 功能到我们的 shell 中
fn main(){ loop { print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let mut parts = input.trim().split_whitespace(); let command = parts.next().unwrap(); let args = parts; match command { "cd" => { // default to '/' as new directory if one was not provided let new_dir = args.peekable().peek().map_or("/", |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { eprintln!("{}", e); } }, command => { let mut child = Command::new(command) .args(args) .spawn() .unwrap(); child.wait(); } } } } 复制代码
错误处理
exit
fn main(){ loop { print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let mut parts = input.trim().split_whitespace(); let command = parts.next().unwrap(); let args = parts; match command { "cd" => { let new_dir = args.peekable().peek().map_or("/", |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { eprintln!("{}", e); } }, "exit" => return, command => { let child = Command::new(command) .args(args) .spawn(); // gracefully handle malformed user input match child { Ok(mut child) => { child.wait(); }, Err(e) => eprintln!("{}", e), }; } } } } 复制代码
管道符
-
如果没有管道操作符的功能的 shell 是很难用于实际生产环境的。如果你不熟悉这个特性,可以使用
|
字符告诉 shell 将第一个命令的结果输出重定向到第二个命令的输入。例如,运行ls | grep Cargo
会触发以下操作:-
ls
将列出当前目录中的所有文件和目录 -
shell 将通过管道将以上的文件和目录列表输入到
grep
-
grep
将过滤这个列表,并只输出文件名包含字符Cargo
的文件
-
-
shell 的最后一次迭代包括了对管道的基础支持。要了解管道和 IO 重定向的其他功能,可以参考这个文章
fn main(){ loop { print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); // must be peekable so we know when we are on the last command let mut commands = input.trim().split(" | ").peekable(); let mut previous_command = None; while let Some(command) = commands.next() { let mut parts = command.trim().split_whitespace(); let command = parts.next().unwrap(); let args = parts; match command { "cd" => { let new_dir = args.peekable().peek() .map_or("/", |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { eprintln!("{}", e); } previous_command = None; }, "exit" => return, command => { let stdin = previous_command .map_or( Stdio::inherit(), |output: Child| Stdio::from(output.stdout.unwrap()) ); let stdout = if commands.peek().is_some() { // there is another command piped behind this one // prepare to send output to the next command Stdio::piped() } else { // there are no more commands piped behind this one // send output to shell stdout Stdio::inherit() }; let output = Command::new(command) .args(args) .stdin(stdin) .stdout(stdout) .spawn(); match output { Ok(output) => { previous_command = Some(output); }, Err(e) => { previous_command = None; eprintln!("{}", e); }, }; } } } if let Some(mut final_command) = previous_command { // block until the final command has finished final_command.wait(); } } } 复制代码
结语
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 在 Android Studio 里使用构建分析器提升构建性能
- 使用 Docker 构建
- 使用 webpack 构建应用
- 使用Dockerfile构建镜像
- 使用模式构建:总结
- 使用 AutoAI 自动构建模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构、算法与应用(原书第2版)
Sartaj Sahni / 王立柱、刘志红 / 机械工业出版社 / 2015-4 / 79.00元
《数据结构、算法与应用——C++语言描述》是享有盛誉的数据结构教科书的第2版。它完整地包含了基本数据结构的内容,是CS2课程的理想用书。作者Sartaj Sahni通过循循善诱的讲解、直观具体的讨论和基于现实的应用,让读者轻松、愉快地学习。新版书着重利用标准模板库(STL),把书中开发的数据结构和算法与相应的STL实现方法相互关联。本书还增加了很多新的实例和练习题。 书中的应用实例是它的特色......一起来看看 《数据结构、算法与应用(原书第2版)》 这本书的介绍吧!