Now that Hacker School is over, I finally have some spare time to blog about the projects I built. This is one I built about 2/3rds of the way into the batch.
I've been writing various small projects in Rust in order to learn the language. My most recent project is a shell! Since obviously it'd be a ton of work to make an actually decent shell, I instead opted to build an different kind of shell. It uses emoji! You can clone the repository on Github, and run rustc shell.rs
to compile.
Once you start ./shell
, you should see the prompt string:
Yes, the prompt string is a sea shell. That's how you know you're in emoji shell! Intuitive, no? To run basic commands, you can just type them:
That little running person indicates the program is running, and the running icon with a cross through it tells you the program has stopped. The number is the exit code.
Emoji shell also has a neat command line arguments feature — you don't need those pesky double quotes, even if an argument has spaces! Just separate all arguments with tomatoes, and emoji shell will take care of the rest. You're probably thinking "of all the emoji, why tomatoes?" right now. That's a good question.
(If you want a tomato in one of your arguments, sorry, you're out of luck.)
To print the current working directory, you can use the location pin. To cd
, use the car to drive to your desired location. Don't forget the tomato!
ls
is a magnifying glass icon, and I'm sure you can guess what cat
is.
Building the Shell in Rust
Building the shell in Rust was relatively simple. I started with the code from the University of Virginia's OS class. Sadly, the code was over a month old. In Rust terms, this means it's basically as old as the hills, and can't be expected to run any more. (I feel like there's probably a rust pun somewhere in there.) I had to adapt it to Rust's new std::io::Process
. Getting it to create a process with stdin and out hooked up to Rust's stdin and out was a little difficult, but I managed to make it work:
let config = process::ProcessConfig {
program: cmd,
args: argv,
cwd: Some(&self.cwd),
stdin: process::InheritFd(0),
stdout: process::InheritFd(1),
stderr: process::InheritFd(2),
.. process::ProcessConfig::new()
};
let p = Process::configure(config);
let status = p.unwrap().wait();
By default, the stdin
and stdout
for a process create new pipes for sending and collecting data. Since we're writing a shell, this is not what we want! We want the subprocess to send its stdin, out, and err to the same place as out Rust program. stderr: process::InheritFd()
gives us the file descriptor of a file with a certain number, which allows us to pass it to the subprocess.
The rest of the shell was somewhat trivial. There's just a loop that gets repeated for each command, and the command is split along tomatoes, and run. There was a little bit of trickiness with the unicode characters, namely needing \u
for 16 bit unicode characters and \U
for 32 bit, but besides that, the rest was relatively simple! If I had more time to improve it, it'd be fun to add piping the stdout of one process to the stdin of another. Thankfully, there happens to be an pipe emoji.