内容简介:正文开始终端模拟器(通常简称为终端)就是一个“窗口”,是的,它运行一个基于文本的程序,默认情况下,它就是你登陆的 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 自动构建模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Where Wizards Stay Up Late
Katie Hafner / Simon & Schuster / 1998-1-21 / USD 16.00
Twenty five years ago, it didn't exist. Today, twenty million people worldwide are surfing the Net. "Where Wizards Stay Up Late" is the exciting story of the pioneers responsible for creating the most......一起来看看 《Where Wizards Stay Up Late》 这本书的介绍吧!