- 原文地址:www.joshmcguigan.com/blog/build-…
- 原文做者:Josh Mcguigan
- 译文出自:github.com/suhanyujie
- 本文永久连接: github.com/suhanyujie/…
- 译者:suhanyujie
正文开始html
终端模拟器(一般简称为终端)就是一个“窗口”,是的,它运行一个基于文本的程序,默认状况下,它就是你登录的 shell (也就是 Ubuntu 下的 bash)。当你在窗口中键入字符时,终端除了将这些字符发送到 shell (或其余程序)的 stdin 以外,还会在窗口中绘制这些字符。 shell 输出到 stdout 和 stderr 的字符被发送到终端,终端在窗口中绘制这些字符。node
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();
}
复制代码
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
命令来尝试一下吧。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();
}
}
复制代码
cd
命令。要了解为何 cd 必须是 shell 的内建功能,请查看这个连接。处理内建的命令,其实是一个名为 cd
的程序。这里有关于这种二象性的解释。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
会触发如下操做:git
ls
将列出当前目录中的全部文件和目录grep
grep
将过滤这个列表,并只输出文件名包含字符 Cargo
的文件shell 的最后一次迭代包括了对管道的基础支持。要了解管道和 IO 重定向的其余功能,能够参考这个文章github
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();
}
}
}
复制代码