Last active
September 14, 2016 21:43
-
-
Save porterjamesj/9c196bdb9d4826b027cef76920f4701a to your computer and use it in GitHub Desktop.
Tiny shell written for a workshop at Recurse Center.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Small description: | |
What it does: | |
1. first this | |
2. then this | |
""" | |
import sys | |
import os | |
import re | |
def export(args): | |
for spec in args[1:]: | |
key, value = spec.split("=") | |
os.environ[key] = value | |
BUILTINS = { | |
"cd": lambda args: os.chdir(args[1]), | |
"exit": lambda args: exit(0), | |
"export": export | |
} | |
def expand_user(input): | |
return [os.path.expanduser(s) for s in input] | |
def expand_env_var(s): | |
"""This substitutes an environment variable into the input if | |
necessary. The way env var substitution works in this shell is | |
that if we have some input like 'blah bla $(FOO) blah', the | |
`$(FOO)` bit will be substituted with the value of the FOO | |
variable from the environment. | |
This is achieved using . . . regular expressions! re.sub takes a | |
regex, and a function. the function is called with the resulting | |
Match object everytime the regex matches, and the entire substring | |
that matched is replaced with the return value of the function. | |
The regex matches: | |
- a literal dollar sign `/$`, | |
- followed by a literal open paren `\(`, | |
- followed by any number of any characters .*. | |
these are captured via being wrapped in parens, since these are | |
the name of the variable we want to extract from the environment | |
- followed by a literal close paren `\)` | |
the function extracts the first capturing group (the name of the | |
env var) using m.group, and then pulls it out of the process | |
environment (using os.environ) | |
re.sub then handles the replacement. voila! | |
""" | |
return re.sub( | |
"\$\((.*)\)", | |
lambda m: os.environ[m.group(1)], | |
s | |
) | |
def expand_env_vars(input): | |
return [expand_env_var(s) for s in input] | |
def process_input(input): | |
user_expanded = expand_user(input) | |
env_var_expanded = expand_env_vars(user_expanded) | |
return env_var_expanded | |
def execute(program_or_builtin, args): | |
if BUILTINS.get(program_or_builtin): | |
BUILTINS[program_or_builtin](args) | |
else: | |
# fork causes this process to turn split into two! a "parent" | |
# and a "child". in the parent, fork returns the process id | |
# ("pid") of the child. in the child, fork returns 0 | |
# | |
# so the if statement checks whether we're the parent or the | |
# child after the fork happens | |
# | |
# the point of doing this forking is so that we have another | |
# process to run the program the user requested in! | |
pid = os.fork() | |
if pid == 0: | |
# we're in the child | |
# run the program the user wants! | |
os.execvp(program_or_builtin, args) | |
else: | |
# we're in the parent | |
# we're going to wait for the child to return using os.waitpid | |
os.waitpid(pid, 0) | |
def shell(): | |
while True: | |
sys.stdout.write("$ ") | |
input = raw_input() | |
split_input = input.split() | |
processed_input = process_input(split_input) | |
execute(processed_input[0], processed_input) | |
if __name__ == "__main__": | |
shell() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment