Skip to content

Instantly share code, notes, and snippets.

@porterjamesj
Last active September 14, 2016 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save porterjamesj/9c196bdb9d4826b027cef76920f4701a to your computer and use it in GitHub Desktop.
Save porterjamesj/9c196bdb9d4826b027cef76920f4701a to your computer and use it in GitHub Desktop.
Tiny shell written for a workshop at Recurse Center.
"""
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