- 原文地址: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();
}
}
}
複製代碼