#!/usr/bin/env python

import sys, os, re

# begin loop:
# - reading from stdin
# - forking a child
# - executing a new process in the child

def main():
	while True:
		sys.stdout.write(os.environ['PROMPT'])
		line = sys.stdin.readline()

		commands = split_on_pipes(line)
		placeholder_in = sys.stdin
		placeholder_out = sys.stdout
		pipe = []
		pids = []

		for line in commands:
			args = line.split()
			command = args[0]

			if command in BUILTINS:
				# run built-in instead of doing fork + exec
				run_builtin(command, args)
			else:
				# if command is not the last command
				if (commands.index(line) + 1) < len(commands):
					pipe = os.pipe() # file descriptors
					placeholder_out = pipe[1]
				else:
					placeholder_out = sys.stdout

				pid = fork_and_exec(command, args, placeholder_out, placeholder_in)
				pids.append(pid)

				if type(placeholder_out) is int:
					os.close(placeholder_out)

				if type(placeholder_in) is int:
					os.close(placeholder_in)

				if commands.index(line) > 0:
					placeholder_in = pipe[0]

		for id in pids:
			wait_for_child(id)

def wait_for_child(pid):
	try:
		os.waitpid(pid, 0)
	except:
		None	

# returns PID of child process
def fork_and_exec(command, args, placeholder_out, placeholder_in):
	pid = os.fork()
	if pid == 0: # inside child process
		if type(placeholder_out) is int:
			sys.stdout = os.fdopen(placeholder_out, 'w')
			os.close(placeholder_out)

		if type(placeholder_in) is int:
			sys.stdin = os.fdopen(placeholder_in, 'r')
			os.close(placeholder_in)

		try:
			os.execvp(command, args) # actual exec
		except:
			print "Error: command not found."
	return pid


def run_builtin(command, args):
	try:
		BUILTINS[command](args[1])
	except:
		BUILTINS[command]()

# returns an array of command strings
def split_on_pipes(line):
	matches = re.findall("([^\"'|]+)|[\"']([^\"']+)[\"']", line)
	commands = []
	for match in matches:
		for string in match:
			if string != '':
				commands.append(string.strip())
	return commands

def set(args):
  key, value = args.split('=')
  os.environ[key] = value

BUILTINS = {
	'cd': lambda path: os.chdir(''.join(path)),
	'exit': lambda exit_code=0: sys.exit(int(exit_code)),
	'set': lambda args: set(args) # can't do variable assignment in Python lambda
}

os.environ['PROMPT'] = "=> "

main()

@@ -34,17 +34,29 @@
 , os, re
+, subprocess
 %0A
-
 %0A# begin
@@ -396,16 +396,46 @@
 %09%09args =
+ %5Bexpand(string) for string in
  line.sp
@@ -439,16 +439,17 @@
 .split()
+%5D
 %0A%09%09%09comm
@@ -1639,13 +1639,10 @@
 nt %22
-Error
+%25s
 : co
@@ -1660,10 +1660,47 @@
 ound
-.
 %22
+ %25 command%0A%09%09%09sys.exit(1) # exit child
 %0A%09re
@@ -2052,16 +2052,16 @@
 trip())%0A
-
 %09return 
@@ -2070,16 +2070,626 @@
 mmands%0A%0A
+# support different types of expansion%0Adef expand(string):%0A%09# variable expansion%0A%09if re.match(%22%5C$%5Cw+%22, string):%0A%09%09return os.environ%5Bstring%5B1:%5D%5D%0A%0A%09# arithmetic expansion%0A%09elif re.match(%22%5C$%5C(%5C((%5B%5Cw%5CW%5Cs%5D*)%5C)%5C)%22, string):%0A%09%09expr = re.match(%22%5C$%5C(%5C((%5B%5Cw%5CW%5Cs%5D*)%5C)%5C)%22, string).group(1)%0A%09%09return str(eval(expr))%0A%0A%09# command expansion%0A%09elif re.match(%22%5C$%5C((%5B%5Cw%5CW%5Cs%5D*)%5C)%22, string):%0A%09%09expr = re.match(%22%5C$%5C((%5B%5Cw%5CW%5Cs%5D*)%5C)%22, string).group(1)%0A%09%09p = subprocess.Popen(%5Bexpr%5D, stdout=subprocess.PIPE, stderr=subprocess.PIPE)%0A%09%09# get the output of the command%0A%09%09out, _ = p.communicate()%0A%09%09return out%5B0:-1%5D%0A%0A%09else:%0A%09%09return string%0A%0A
 def set(

Enable a few types of shell expansion