From 4da449e5a24d87817007f99c0344fd937636e9ac Mon Sep 17 00:00:00 2001 From: root Date: Sat, 30 Jul 2016 11:35:30 +0800 Subject: [PATCH 01/45] Add some features --- README.md | 4 ++++ yosh/shell.py | 26 +++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1843909..e6e9551 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # yosh - Your Own SHell +This is fork from https://github.com/supasate/yosh + +And it's modifed by Ted,fixed some problems and made it more looks like a noraml shell + A simple shell written in Python The implementation details can be found at **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/) diff --git a/yosh/shell.py b/yosh/shell.py index 6e67b90..97bd69a 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -1,6 +1,8 @@ import os import sys import shlex +import getpass +import socket from yosh.constants import * from yosh.builtins import * @@ -30,7 +32,11 @@ def execute(cmd_tokens): if pid == 0: # Child process # Replace the child shell process with the program called with exec - os.execvp(cmd_tokens[0], cmd_tokens) + #Modifed by Ted,make a beautiful error output + try: + os.execvp(cmd_tokens[0], cmd_tokens) + except OSError,e: + print e elif pid > 0: # Parent process while True: @@ -51,17 +57,27 @@ def shell_loop(): while status == SHELL_STATUS_RUN: # Display a command prompt - sys.stdout.write('> ') + # Modifed by Ted,make it more looks like bash command prompt + if os.getcwd() == os.getenv('HOME'): + dir = "~" + else: + dir = os.getcwd() + if os.geteuid() != 0: + sys.stdout.write('['+getpass.getuser()+'@'+socket.gethostname()+' '+dir+']$ ') + else: + sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() # Read command input cmd = sys.stdin.readline() - + # Tokenize the command input cmd_tokens = tokenize(cmd) - # Execute the command and retrieve new status - status = execute(cmd_tokens) + # Fix a bug with inputing nothing + if cmd_tokens: + # Execute the command and retrieve new status + status = execute(cmd_tokens) # Register a built-in function to built-in command hash map From f4801d71d3ec56dda9114bb124f04e303f0c163c Mon Sep 17 00:00:00 2001 From: Ted Date: Sat, 30 Jul 2016 12:06:41 +0800 Subject: [PATCH 02/45] Fix bugs with Crtl+C --- yosh/shell.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 97bd69a..518589e 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -37,11 +37,16 @@ def execute(cmd_tokens): os.execvp(cmd_tokens[0], cmd_tokens) except OSError,e: print e + elif pid > 0: # Parent process while True: # Wait response status from its child process (identified with pid) - wpid, status = os.waitpid(pid, 0) + try: + wpid, status = os.waitpid(pid, 0) + # Fix a bug with ctrl+c,modifed by Ted + except KeyboardInterrupt: + break # Finish waiting if its child process exits normally or is # terminated by a signal @@ -68,8 +73,11 @@ def shell_loop(): sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() - # Read command input - cmd = sys.stdin.readline() + try: + # Read command input + cmd = sys.stdin.readline() + except KeyboardInterrupt: + print "" # Tokenize the command input cmd_tokens = tokenize(cmd) From b629b7b1043169420e0c3332fac368f98ac3cde7 Mon Sep 17 00:00:00 2001 From: Ted Date: Sat, 30 Jul 2016 13:02:52 +0800 Subject: [PATCH 03/45] Fix bugs with some commands --- yosh/shell.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 518589e..b8f21c1 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -3,6 +3,7 @@ import shlex import getpass import socket +import signal from yosh.constants import * from yosh.builtins import * @@ -13,7 +14,6 @@ def tokenize(string): return shlex.split(string) - def execute(cmd_tokens): # Extract command name and arguments from tokens cmd_name = cmd_tokens[0] @@ -73,19 +73,26 @@ def shell_loop(): sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() + # Modifed by Ted,do not receive Ctrl+Z signal + signal.signal(signal.SIGTSTP, signal.SIG_IGN) + #The bugs with receiving wrong command had fixed by Ted try: # Read command input cmd = sys.stdin.readline() except KeyboardInterrupt: - print "" - - # Tokenize the command input - cmd_tokens = tokenize(cmd) - + print "\nRead wrong command input" + try: + # Tokenize the command input + cmd_tokens = tokenize(cmd) + except: + print "Error when receiving the command" # Fix a bug with inputing nothing - if cmd_tokens: - # Execute the command and retrieve new status - status = execute(cmd_tokens) + try: + if cmd_tokens: + # Execute the command and retrieve new status + status = execute(cmd_tokens) + except: + print "Error when running the command" # Register a built-in function to built-in command hash map From c94084b5b020d80de6f812f9bcf4d5a564d0fce3 Mon Sep 17 00:00:00 2001 From: Ted Date: Sat, 30 Jul 2016 13:51:57 +0800 Subject: [PATCH 04/45] Added getenv and rewrite something --- yosh/builtins/__init__.py | 1 + yosh/builtins/exit.py | 1 - yosh/builtins/getenv.py | 7 +++++++ yosh/shell.py | 37 +++++++------------------------------ 4 files changed, 15 insertions(+), 31 deletions(-) create mode 100644 yosh/builtins/getenv.py diff --git a/yosh/builtins/__init__.py b/yosh/builtins/__init__.py index c9bf801..b231d30 100644 --- a/yosh/builtins/__init__.py +++ b/yosh/builtins/__init__.py @@ -1,2 +1,3 @@ from yosh.builtins.cd import cd from yosh.builtins.exit import exit +from yosh.builtins.getenv import getenv diff --git a/yosh/builtins/exit.py b/yosh/builtins/exit.py index 1328a1e..8b95125 100644 --- a/yosh/builtins/exit.py +++ b/yosh/builtins/exit.py @@ -1,5 +1,4 @@ from yosh.constants import * - def exit(args): return SHELL_STATUS_STOP diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py new file mode 100644 index 0000000..d7b96a5 --- /dev/null +++ b/yosh/builtins/getenv.py @@ -0,0 +1,7 @@ +import os +from yosh.constants import * + + +def getenv(args): + print os.getenv(args[0]) + return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index b8f21c1..e1edb2b 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -4,6 +4,7 @@ import getpass import socket import signal +import subprocess from yosh.constants import * from yosh.builtins import * @@ -22,36 +23,10 @@ def execute(cmd_tokens): # If the command is a built-in command, invoke its function with arguments if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) - - # Fork a child shell process - # If the current process is a child process, its `pid` is set to `0` - # else the current process is a parent process and the value of `pid` - # is the process id of its child process. - pid = os.fork() - - if pid == 0: - # Child process - # Replace the child shell process with the program called with exec - #Modifed by Ted,make a beautiful error output - try: - os.execvp(cmd_tokens[0], cmd_tokens) - except OSError,e: - print e - - elif pid > 0: - # Parent process - while True: - # Wait response status from its child process (identified with pid) - try: - wpid, status = os.waitpid(pid, 0) - # Fix a bug with ctrl+c,modifed by Ted - except KeyboardInterrupt: - break - - # Finish waiting if its child process exits normally or is - # terminated by a signal - if os.WIFEXITED(status) or os.WIFSIGNALED(status): - break + # Written in beautiful sentences to run the command,modifed by Ted + sh = subprocess.Popen(cmd_tokens) + # Parent process wait for child process + sh.communicate() # Return status indicating to wait for next command in shell_loop return SHELL_STATUS_RUN @@ -104,6 +79,8 @@ def register_command(name, func): def init(): register_command("cd", cd) register_command("exit", exit) + register_command("getenv", getenv) + def main(): From 3cba7752c95b4a42c342f3bf0aaf7957dedc40f9 Mon Sep 17 00:00:00 2001 From: Ted Date: Thu, 15 May 2014 09:36:01 +0800 Subject: [PATCH 05/45] get the --- yosh/shell.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index e1edb2b..3f6684d 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -13,8 +13,11 @@ def tokenize(string): - return shlex.split(string) - + token=shlex.split(string) + for i, el in enumerate(token): + if el.startswith('$'): + token[i] = os.getenv(token[i][1:]) + return token def execute(cmd_tokens): # Extract command name and arguments from tokens cmd_name = cmd_tokens[0] From 729471720e7f930f314110b4c7fd210ec79cdb16 Mon Sep 17 00:00:00 2001 From: Ted Date: Thu, 15 May 2014 10:46:56 +0800 Subject: [PATCH 06/45] Fix ctrl+c but ctrl+z can't stop command --- yosh/shell.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 3f6684d..b454ed9 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -18,7 +18,12 @@ def tokenize(string): if el.startswith('$'): token[i] = os.getenv(token[i][1:]) return token + +def handler_kill(signum, frame): + raise OSError("Killed!") + def execute(cmd_tokens): + if cmd_tokens: # Extract command name and arguments from tokens cmd_name = cmd_tokens[0] cmd_args = cmd_tokens[1:] @@ -26,13 +31,14 @@ def execute(cmd_tokens): # If the command is a built-in command, invoke its function with arguments if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) + global sh + signal.signal(signal.SIGINT,handler_kill) # Written in beautiful sentences to run the command,modifed by Ted sh = subprocess.Popen(cmd_tokens) # Parent process wait for child process sh.communicate() - - # Return status indicating to wait for next command in shell_loop - return SHELL_STATUS_RUN + # Return status indicating to wait for next command in shell_loop + return SHELL_STATUS_RUN def shell_loop(): @@ -51,14 +57,15 @@ def shell_loop(): sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() - # Modifed by Ted,do not receive Ctrl+Z signal + # Modifed by Ted,do not receive Ctrl signal signal.signal(signal.SIGTSTP, signal.SIG_IGN) + signal.signal(signal.SIGINT,signal.SIG_IGN) #The bugs with receiving wrong command had fixed by Ted try: # Read command input cmd = sys.stdin.readline() - except KeyboardInterrupt: - print "\nRead wrong command input" + except KeyboardInterrupt,e: + print e try: # Tokenize the command input cmd_tokens = tokenize(cmd) @@ -66,11 +73,10 @@ def shell_loop(): print "Error when receiving the command" # Fix a bug with inputing nothing try: - if cmd_tokens: - # Execute the command and retrieve new status - status = execute(cmd_tokens) - except: - print "Error when running the command" + # Execute the command and retrieve new status + status = execute(cmd_tokens) + except OSError,e: + print e # Register a built-in function to built-in command hash map From 6e3e1aad0751ff8e4d1c9dcad9202dcfa2b075e9 Mon Sep 17 00:00:00 2001 From: Ted Date: Thu, 15 May 2014 11:10:08 +0800 Subject: [PATCH 07/45] added the export command --- yosh/builtins/__init__.py | 1 + yosh/builtins/export.py | 8 ++++++++ yosh/shell.py | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 yosh/builtins/export.py diff --git a/yosh/builtins/__init__.py b/yosh/builtins/__init__.py index b231d30..d9f3722 100644 --- a/yosh/builtins/__init__.py +++ b/yosh/builtins/__init__.py @@ -1,3 +1,4 @@ from yosh.builtins.cd import cd from yosh.builtins.exit import exit from yosh.builtins.getenv import getenv +from yosh.builtins.export import export diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py new file mode 100644 index 0000000..b911935 --- /dev/null +++ b/yosh/builtins/export.py @@ -0,0 +1,8 @@ +import os +from yosh.constants import * + + +def export(args): + var=args[0].split('=', 1 ); + os.environ[var[0]] = var[1] + return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index b454ed9..4508461 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -89,7 +89,7 @@ def init(): register_command("cd", cd) register_command("exit", exit) register_command("getenv", getenv) - + register_command("export", export) def main(): From 276aa5cfe9e8250f009fd02cf66eafcc89d29bc8 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 10:57:53 +0800 Subject: [PATCH 08/45] Use print() for compatibility with Python 2 and 3. --- yosh/shell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 4508461..ebc9b05 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -65,18 +65,18 @@ def shell_loop(): # Read command input cmd = sys.stdin.readline() except KeyboardInterrupt,e: - print e + print (e) try: # Tokenize the command input cmd_tokens = tokenize(cmd) except: - print "Error when receiving the command" + print ("Error when receiving the command") # Fix a bug with inputing nothing try: # Execute the command and retrieve new status status = execute(cmd_tokens) except OSError,e: - print e + print (e) # Register a built-in function to built-in command hash map From d71898bfa665c7f25868b344b3ba5ce2c223bdfe Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 11:06:14 +0800 Subject: [PATCH 09/45] Update the comment --- yosh/shell.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index ebc9b05..9090905 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -32,8 +32,10 @@ def execute(cmd_tokens): if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) global sh + # Wait for a kill signal signal.signal(signal.SIGINT,handler_kill) - # Written in beautiful sentences to run the command,modifed by Ted + # Written in beautiful sentences to run the command + # Spawn a child process sh = subprocess.Popen(cmd_tokens) # Parent process wait for child process sh.communicate() @@ -46,7 +48,7 @@ def shell_loop(): while status == SHELL_STATUS_RUN: # Display a command prompt - # Modifed by Ted,make it more looks like bash command prompt + # Make it more looks like bash command prompt if os.getcwd() == os.getenv('HOME'): dir = "~" else: @@ -57,7 +59,7 @@ def shell_loop(): sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() - # Modifed by Ted,do not receive Ctrl signal + # Do not receive Ctrl signal signal.signal(signal.SIGTSTP, signal.SIG_IGN) signal.signal(signal.SIGINT,signal.SIG_IGN) #The bugs with receiving wrong command had fixed by Ted From 6dd8713d604545585ef881e1dd5c0fdb41920d48 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 11:11:40 +0800 Subject: [PATCH 10/45] Changes Readme.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index e6e9551..1843909 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # yosh - Your Own SHell -This is fork from https://github.com/supasate/yosh - -And it's modifed by Ted,fixed some problems and made it more looks like a noraml shell - A simple shell written in Python The implementation details can be found at **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/) From 86b452b158bb2e66d25faa911e114e86b0aa6178 Mon Sep 17 00:00:00 2001 From: ted Date: Mon, 1 Aug 2016 11:43:32 +0800 Subject: [PATCH 11/45] [PEP8] Fix the code --- yosh/builtins/exit.py | 1 + yosh/builtins/export.py | 2 +- yosh/shell.py | 79 ++++++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/yosh/builtins/exit.py b/yosh/builtins/exit.py index 8b95125..1328a1e 100644 --- a/yosh/builtins/exit.py +++ b/yosh/builtins/exit.py @@ -1,4 +1,5 @@ from yosh.constants import * + def exit(args): return SHELL_STATUS_STOP diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py index b911935..f416594 100644 --- a/yosh/builtins/export.py +++ b/yosh/builtins/export.py @@ -3,6 +3,6 @@ def export(args): - var=args[0].split('=', 1 ); + var = args[0].split('=', 1) os.environ[var[0]] = var[1] return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index 9090905..40f6d17 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -13,34 +13,37 @@ def tokenize(string): - token=shlex.split(string) + token = shlex.split(string) for i, el in enumerate(token): if el.startswith('$'): - token[i] = os.getenv(token[i][1:]) + token[i] = os.getenv(token[i][1:]) return token + def handler_kill(signum, frame): raise OSError("Killed!") + def execute(cmd_tokens): - if cmd_tokens: - # Extract command name and arguments from tokens - cmd_name = cmd_tokens[0] - cmd_args = cmd_tokens[1:] - - # If the command is a built-in command, invoke its function with arguments - if cmd_name in built_in_cmds: - return built_in_cmds[cmd_name](cmd_args) - global sh - # Wait for a kill signal - signal.signal(signal.SIGINT,handler_kill) - # Written in beautiful sentences to run the command - # Spawn a child process - sh = subprocess.Popen(cmd_tokens) - # Parent process wait for child process - sh.communicate() - # Return status indicating to wait for next command in shell_loop - return SHELL_STATUS_RUN + if cmd_tokens: + # Extract command name and arguments from tokens + cmd_name = cmd_tokens[0] + cmd_args = cmd_tokens[1:] + + # If the command is a built-in command, + # invoke its function with arguments + if cmd_name in built_in_cmds: + return built_in_cmds[cmd_name](cmd_args) + global sh + # Wait for a kill signal + signal.signal(signal.SIGINT, handler_kill) + # Written in beautiful sentences to run the command + # Spawn a child process + sh = subprocess.Popen(cmd_tokens) + # Parent process wait for child process + sh.communicate() + # Return status indicating to wait for next command in shell_loop + return SHELL_STATUS_RUN def shell_loop(): @@ -50,35 +53,37 @@ def shell_loop(): # Display a command prompt # Make it more looks like bash command prompt if os.getcwd() == os.getenv('HOME'): - dir = "~" + dir = "~" else: - dir = os.getcwd() + dir = os.getcwd() if os.geteuid() != 0: - sys.stdout.write('['+getpass.getuser()+'@'+socket.gethostname()+' '+dir+']$ ') + sys.stdout.write( + '[' + getpass.getuser() + '@' + + socket.gethostname() + ' '+dir+']$ ') else: - sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') + sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') sys.stdout.flush() # Do not receive Ctrl signal signal.signal(signal.SIGTSTP, signal.SIG_IGN) - signal.signal(signal.SIGINT,signal.SIG_IGN) - #The bugs with receiving wrong command had fixed by Ted + signal.signal(signal.SIGINT, signal.SIG_IGN) + # The bugs with receiving wrong command try: - # Read command input - cmd = sys.stdin.readline() - except KeyboardInterrupt,e: - print (e) + # Read command input + cmd = sys.stdin.readline() + except KeyboardInterrupt, e: + print (e) try: - # Tokenize the command input - cmd_tokens = tokenize(cmd) + # Tokenize the command input + cmd_tokens = tokenize(cmd) except: - print ("Error when receiving the command") + print ("Error when receiving the command") # Fix a bug with inputing nothing try: - # Execute the command and retrieve new status - status = execute(cmd_tokens) - except OSError,e: - print (e) + # Execute the command and retrieve new status + status = execute(cmd_tokens) + except OSError, e: + print (e) # Register a built-in function to built-in command hash map From 1bcbf1927cb76947e52aee17ea8b03ee3f811189 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 13:17:11 +0800 Subject: [PATCH 12/45] Fix the global variable and make the output beautiful --- yosh/shell.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 40f6d17..e049d96 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -34,7 +34,6 @@ def execute(cmd_tokens): # invoke its function with arguments if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) - global sh # Wait for a kill signal signal.signal(signal.SIGINT, handler_kill) # Written in beautiful sentences to run the command @@ -54,14 +53,16 @@ def shell_loop(): # Make it more looks like bash command prompt if os.getcwd() == os.getenv('HOME'): dir = "~" + elif os.getcwd() == os.getenv('HOME') + "/yosh": + dir = "" else: - dir = os.getcwd() + dir = os.getcwd().split('/')[-1] if os.geteuid() != 0: sys.stdout.write( '[' + getpass.getuser() + '@' + - socket.gethostname() + ' '+dir+']$ ') + socket.gethostname().split('.')[0] + ' '+dir+']$ ') else: - sys.stdout.write('[root@'+socket.gethostname()+' '+dir+']# ') + sys.stdout.write('[root@'+socket.gethostname().split('.')[0]+' '+dir+']# ') sys.stdout.flush() # Do not receive Ctrl signal From 6d66ca2c751102dc0e69bfcbcd7eb9c31a30459f Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 13:20:32 +0800 Subject: [PATCH 13/45] [PEP8]Fix the code --- yosh/shell.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index e049d96..d1b1011 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -60,9 +60,12 @@ def shell_loop(): if os.geteuid() != 0: sys.stdout.write( '[' + getpass.getuser() + '@' + - socket.gethostname().split('.')[0] + ' '+dir+']$ ') + socket.gethostname().split('.')[0] + + ' '+dir+']$ ') else: - sys.stdout.write('[root@'+socket.gethostname().split('.')[0]+' '+dir+']# ') + sys.stdout.write( + '[root@' + socket.gethostname().split('.')[0] + + ' ' + dir + ']# ') sys.stdout.flush() # Do not receive Ctrl signal From 1359c503a52fbbf29e1f9a90ab4fa5805cda1b29 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 16:00:35 +0800 Subject: [PATCH 14/45] A bit change --- yosh/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yosh/shell.py b/yosh/shell.py index d1b1011..c909e0b 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -61,7 +61,7 @@ def shell_loop(): sys.stdout.write( '[' + getpass.getuser() + '@' + socket.gethostname().split('.')[0] + - ' '+dir+']$ ') + ' ' + dir + ']$ ') else: sys.stdout.write( '[root@' + socket.gethostname().split('.')[0] + From 36524db841e4dc14c3326bb76cd184599ff06ca7 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 16:15:18 +0800 Subject: [PATCH 15/45] Support for Windows --- yosh/shell.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index c909e0b..8bcd3a1 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -5,6 +5,7 @@ import socket import signal import subprocess +import platform from yosh.constants import * from yosh.builtins import * @@ -57,15 +58,19 @@ def shell_loop(): dir = "" else: dir = os.getcwd().split('/')[-1] - if os.geteuid() != 0: - sys.stdout.write( - '[' + getpass.getuser() + '@' + - socket.gethostname().split('.')[0] + - ' ' + dir + ']$ ') + if platform.system() != "Windows": + if os.geteuid() != 0: + sys.stdout.write( + '[' + getpass.getuser() + '@' + + socket.gethostname().split('.')[0] + + ' ' + dir + ']$ ') + else: + sys.stdout.write( + '[root@' + socket.gethostname().split('.')[0] + + ' ' + dir + ']# ') else: - sys.stdout.write( - '[root@' + socket.gethostname().split('.')[0] + - ' ' + dir + ']# ') + # Support for Windows + sys.stdout.write(dir + ">") sys.stdout.flush() # Do not receive Ctrl signal From 1385dd13481a5f97d4a2679cbb53f83535e4f4d8 Mon Sep 17 00:00:00 2001 From: ted Date: Mon, 1 Aug 2016 16:46:04 +0800 Subject: [PATCH 16/45] Support for windows --- yosh/shell.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 8bcd3a1..2291a22 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -39,9 +39,16 @@ def execute(cmd_tokens): signal.signal(signal.SIGINT, handler_kill) # Written in beautiful sentences to run the command # Spawn a child process - sh = subprocess.Popen(cmd_tokens) - # Parent process wait for child process - sh.communicate() + if platform.system() != "Windows": + sh = subprocess.Popen(cmd_tokens[0]) + # Parent process wait for child process + sh.communicate() + else: + # Support for Windows,written in bad sentences. + command = "" + for i in cmd_tokens: + command = command + " " + i + os.system(command) # Return status indicating to wait for next command in shell_loop return SHELL_STATUS_RUN @@ -52,13 +59,13 @@ def shell_loop(): while status == SHELL_STATUS_RUN: # Display a command prompt # Make it more looks like bash command prompt - if os.getcwd() == os.getenv('HOME'): - dir = "~" - elif os.getcwd() == os.getenv('HOME') + "/yosh": - dir = "" - else: - dir = os.getcwd().split('/')[-1] if platform.system() != "Windows": + if os.getcwd() == os.getenv('HOME'): + dir = "~" + elif os.getcwd() == os.getenv('HOME') + "/yosh": + dir = "" + else: + dir = os.getcwd().split('/')[-1] if os.geteuid() != 0: sys.stdout.write( '[' + getpass.getuser() + '@' + @@ -70,11 +77,12 @@ def shell_loop(): ' ' + dir + ']# ') else: # Support for Windows - sys.stdout.write(dir + ">") + sys.stdout.write(os.getcwd() + "> ") sys.stdout.flush() # Do not receive Ctrl signal - signal.signal(signal.SIGTSTP, signal.SIG_IGN) + if platform.system() != "Windows": + signal.signal(signal.SIGTSTP, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN) # The bugs with receiving wrong command try: From 249e0d8e82232a9cfb5cae2fdc87abbc6c9d994f Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 17:52:43 +0800 Subject: [PATCH 17/45] add setup.py --- setup.py | 16 ++++++++++++++++ yosh/shell.py | 2 -- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..35dcd06 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup(name='yosh', + version='1.0', + description='Your own shell', + author='Supasate', + author_email='tanyuliang2@gmail.com', + url='https://github.com/supasate/yosh', + packages=['yosh', 'yosh.builtins'], + entry_points=""" + [console_scripts] + yosh = yosh.shell:main + """, + ) diff --git a/yosh/shell.py b/yosh/shell.py index 2291a22..b6ecce6 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -62,8 +62,6 @@ def shell_loop(): if platform.system() != "Windows": if os.getcwd() == os.getenv('HOME'): dir = "~" - elif os.getcwd() == os.getenv('HOME') + "/yosh": - dir = "" else: dir = os.getcwd().split('/')[-1] if os.geteuid() != 0: From 0c32aba711c6c67e99aed0dc506b3c3e18d61e46 Mon Sep 17 00:00:00 2001 From: Ted Date: Mon, 1 Aug 2016 18:03:55 +0800 Subject: [PATCH 18/45] Fix a bug in cd with inputing nothing --- yosh/builtins/cd.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yosh/builtins/cd.py b/yosh/builtins/cd.py index f0a3b52..7e0c8af 100644 --- a/yosh/builtins/cd.py +++ b/yosh/builtins/cd.py @@ -3,5 +3,8 @@ def cd(args): - os.chdir(args[0]) + if args: + os.chdir(args[0]) + else: + os.chdir(os.getenv('HOME')) return SHELL_STATUS_RUN From 67f7bf5339a9577ef669e371925c8d628f12e63c Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 1 Aug 2016 18:49:06 +0700 Subject: [PATCH 19/45] Fix PEP8 --- yosh/builtins/cd.py | 4 ++-- yosh/shell.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/yosh/builtins/cd.py b/yosh/builtins/cd.py index 7e0c8af..51e70b0 100644 --- a/yosh/builtins/cd.py +++ b/yosh/builtins/cd.py @@ -4,7 +4,7 @@ def cd(args): if args: - os.chdir(args[0]) + os.chdir(args[0]) else: - os.chdir(os.getenv('HOME')) + os.chdir(os.getenv('HOME')) return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index b6ecce6..4c317e1 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -37,7 +37,6 @@ def execute(cmd_tokens): return built_in_cmds[cmd_name](cmd_args) # Wait for a kill signal signal.signal(signal.SIGINT, handler_kill) - # Written in beautiful sentences to run the command # Spawn a child process if platform.system() != "Windows": sh = subprocess.Popen(cmd_tokens[0]) From 02348f94a25d5db7c6465465203c30dd994d030c Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 1 Aug 2016 18:49:19 +0700 Subject: [PATCH 20/45] Fix author info --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 35dcd06..4b66c58 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ setup(name='yosh', version='1.0', description='Your own shell', - author='Supasate', - author_email='tanyuliang2@gmail.com', + author='Supasate Choochaisri', + author_email='supasate.c@gmail.com', url='https://github.com/supasate/yosh', packages=['yosh', 'yosh.builtins'], entry_points=""" From 81e747db579324764327213069b378c44a4bae18 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 1 Aug 2016 19:01:34 +0700 Subject: [PATCH 21/45] Reformat coding style --- yosh/shell.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 4c317e1..727b382 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -39,11 +39,12 @@ def execute(cmd_tokens): signal.signal(signal.SIGINT, handler_kill) # Spawn a child process if platform.system() != "Windows": + # Unix support sh = subprocess.Popen(cmd_tokens[0]) # Parent process wait for child process sh.communicate() else: - # Support for Windows,written in bad sentences. + # Windows support command = "" for i in cmd_tokens: command = command + " " + i @@ -57,7 +58,6 @@ def shell_loop(): while status == SHELL_STATUS_RUN: # Display a command prompt - # Make it more looks like bash command prompt if platform.system() != "Windows": if os.getcwd() == os.getenv('HOME'): dir = "~" @@ -65,15 +65,15 @@ def shell_loop(): dir = os.getcwd().split('/')[-1] if os.geteuid() != 0: sys.stdout.write( - '[' + getpass.getuser() + '@' + - socket.gethostname().split('.')[0] + - ' ' + dir + ']$ ') + '[' + getpass.getuser() + '@' + + socket.gethostname().split('.')[0] + + ' ' + dir + ']$ ') else: sys.stdout.write( - '[root@' + socket.gethostname().split('.')[0] + - ' ' + dir + ']# ') + '[root@' + socket.gethostname().split('.')[0] + + ' ' + dir + ']# ') else: - # Support for Windows + # Windows support sys.stdout.write(os.getcwd() + "> ") sys.stdout.flush() @@ -81,23 +81,24 @@ def shell_loop(): if platform.system() != "Windows": signal.signal(signal.SIGTSTP, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN) - # The bugs with receiving wrong command + try: # Read command input cmd = sys.stdin.readline() except KeyboardInterrupt, e: - print (e) + print(e) + try: # Tokenize the command input cmd_tokens = tokenize(cmd) except: - print ("Error when receiving the command") + print("Error when receiving the command") # Fix a bug with inputing nothing try: # Execute the command and retrieve new status status = execute(cmd_tokens) except OSError, e: - print (e) + print(e) # Register a built-in function to built-in command hash map From 135236c73cf4800f78cc7808fe0d71da23e33a51 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 1 Aug 2016 19:12:23 +0700 Subject: [PATCH 22/45] Make code compatible with Python 3 --- yosh/builtins/getenv.py | 2 +- yosh/shell.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py index d7b96a5..4f5f258 100644 --- a/yosh/builtins/getenv.py +++ b/yosh/builtins/getenv.py @@ -3,5 +3,5 @@ def getenv(args): - print os.getenv(args[0]) + print(os.getenv(args[0])) return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index 727b382..8cc049a 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -85,8 +85,9 @@ def shell_loop(): try: # Read command input cmd = sys.stdin.readline() - except KeyboardInterrupt, e: - print(e) + except KeyboardInterrupt: + _, err, _ = sys.exc_info() + print(err) try: # Tokenize the command input @@ -97,8 +98,9 @@ def shell_loop(): try: # Execute the command and retrieve new status status = execute(cmd_tokens) - except OSError, e: - print(e) + except OSError: + _, err, _ = sys.exc_info() + print(err) # Register a built-in function to built-in command hash map From ad9bb47f03617cd73340516198f7ede5c71920ef Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 1 Aug 2016 19:18:54 +0700 Subject: [PATCH 23/45] Add changelog for 0.2.0 --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e6db989..e5e8b69 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2016-08-01, Version 0.2.0 +========================= +* Add `export` built-in command (Yuliang Tan) +* Add `getenv` built-in command (Yuliang Tan) +* Use subprocess module to fork and exec (Yuliang Tan) +* Get an environment variable if a token begins with a dollar sign (Yuliang Tan) +* Support Windows (Yuliang Tan) + 2016-07-08, Version 0.1.0 ========================= * Add main shell lopp (Supasate Choochaisri) From 197e42ee46cc4089b749f0b9b735799c98f71b67 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 2 Aug 2016 00:16:48 +0700 Subject: [PATCH 24/45] Update README about the tutorial branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1843909..2e6a3c8 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ A simple shell written in Python -The implementation details can be found at **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/) +> Note: If you found this repo from the article **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/), you can check out the `tutorial` branch for the source code used in the article. From 3044fd5c5d313451e83e45d9497b3974f6dcf818 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 2 Aug 2016 00:48:34 +0700 Subject: [PATCH 25/45] Add travis to check PEP8 --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..87283ab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +before_install: + - pip install pep8 + +script: + - find . -name \*.py -exec pep8 {} + From 48b7926472f649ee683139d711182e9e83e71743 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 2 Aug 2016 00:51:32 +0700 Subject: [PATCH 26/45] Run travis with various python versions --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 87283ab..9ba4db6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,10 @@ +language: python +python: + - 2.7 + - 3.3 + - 3.4 + - 3.5 + before_install: - pip install pep8 From 85e4a88efb70cf2c56c8ceeafdc95fcff911f1c4 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 2 Aug 2016 01:26:55 +0700 Subject: [PATCH 27/45] [Fix] pass arguments to child process --- yosh/shell.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 8cc049a..45f537e 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -35,14 +35,16 @@ def execute(cmd_tokens): # invoke its function with arguments if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) + # Wait for a kill signal signal.signal(signal.SIGINT, handler_kill) # Spawn a child process if platform.system() != "Windows": # Unix support - sh = subprocess.Popen(cmd_tokens[0]) - # Parent process wait for child process - sh.communicate() + p = subprocess.Popen(cmd_tokens) + # Parent process read data from child process + # and wait for child process to exit + p.communicate() else: # Windows support command = "" From 41087d5b6e4523b8cc10045854311a399502eced Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 2 Aug 2016 01:42:22 +0700 Subject: [PATCH 28/45] Add build badge --- CHANGES.md | 3 +++ README.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e5e8b69..c246f18 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ * Use subprocess module to fork and exec (Yuliang Tan) * Get an environment variable if a token begins with a dollar sign (Yuliang Tan) * Support Windows (Yuliang Tan) +* Fix command arguments are not passed to child process (Supasate Choochaisri) +* Add travis build to check PEP8 (Supasate Choochaisri) +* Add build badge (Supasate Choochaisri) 2016-07-08, Version 0.1.0 ========================= diff --git a/README.md b/README.md index 2e6a3c8..c256072 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # yosh - Your Own SHell +[![Build Status](https://travis-ci.org/supasate/yosh.svg?branch=master)](https://travis-ci.org/supasate/yosh) + A simple shell written in Python > Note: If you found this repo from the article **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/), you can check out the `tutorial` branch for the source code used in the article. From 2a098c6dff023deaaf51fd34f7681f010e233b3e Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 11:32:37 +0800 Subject: [PATCH 29/45] Auto find the command if the command does not exit --- yosh/builtins/export.py | 5 +++-- yosh/builtins/getenv.py | 3 ++- yosh/shell.py | 26 +++++++++++++++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py index f416594..0fe41b7 100644 --- a/yosh/builtins/export.py +++ b/yosh/builtins/export.py @@ -3,6 +3,7 @@ def export(args): - var = args[0].split('=', 1) - os.environ[var[0]] = var[1] + if args: + var = args[0].split('=', 1) + os.environ[var[0]] = var[1] return SHELL_STATUS_RUN diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py index 4f5f258..9e10164 100644 --- a/yosh/builtins/getenv.py +++ b/yosh/builtins/getenv.py @@ -3,5 +3,6 @@ def getenv(args): - print(os.getenv(args[0])) + if args: + print(os.getenv(args[0])) return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index 45f537e..b38817c 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -40,11 +40,27 @@ def execute(cmd_tokens): signal.signal(signal.SIGINT, handler_kill) # Spawn a child process if platform.system() != "Windows": - # Unix support - p = subprocess.Popen(cmd_tokens) - # Parent process read data from child process - # and wait for child process to exit - p.communicate() + found = 0 + for i in os.getenv("PATH").split(":"): + if os.path.exists(i + "/" + cmd_name): + # Fix the cmd_tokens + p = subprocess.Popen(cmd_tokens) + # Parent process read data from child process + # and wait for child process to exit + p.communicate() + found = 1 + break + if found == 0: + for i in os.getenv("PATH").split(":"): + for root, dirs, files in os.walk(i): + for f in files: + if f.find(cmd_tokens[0]) != -1: + print "Do you mean: " + f + found = 1 + break + if found == 0: + print "File Not Found" + else: # Windows support command = "" From 48523baa54fa57501c6bbfda2516b42911e9ee79 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 11:44:58 +0800 Subject: [PATCH 30/45] Set an environment variable if a token has a sign --- CHANGES.md | 2 ++ yosh/shell.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c246f18..870ce1a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Fix command arguments are not passed to child process (Supasate Choochaisri) * Add travis build to check PEP8 (Supasate Choochaisri) * Add build badge (Supasate Choochaisri) +* Auto find the command if the command does not exit (Yuliang Tan) +* Set an environment variable if a token has a `=` sign (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/yosh/shell.py b/yosh/shell.py index b38817c..e5f1832 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -16,8 +16,19 @@ def tokenize(string): token = shlex.split(string) for i, el in enumerate(token): + # Find the `=` sign + if el.find('=') != -1: + if int(el.find('=')) > 0: + if int(el.find('=')) != len(token[i]): + if token[i][int(el.find('='))-1] != "=": + if token[i][int(el.find('='))+1] != "=": + token.append(str(token[i])) + token[i]="export" + break + # Find the dollar sign if el.startswith('$'): - token[i] = os.getenv(token[i][1:]) + token[i] = str(os.getenv(token[i][1:])) + break return token @@ -41,6 +52,7 @@ def execute(cmd_tokens): # Spawn a child process if platform.system() != "Windows": found = 0 + # Auto find the command for i in os.getenv("PATH").split(":"): if os.path.exists(i + "/" + cmd_name): # Fix the cmd_tokens From ffcd39ed1a990bbd6d1f91ade759754219919cc7 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 12:34:54 +0800 Subject: [PATCH 31/45] Redirect Output with subprocess if a token has a > sign --- CHANGES.md | 1 + yosh/shell.py | 34 ++++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 870ce1a..f605532 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ * Add build badge (Supasate Choochaisri) * Auto find the command if the command does not exit (Yuliang Tan) * Set an environment variable if a token has a `=` sign (Yuliang Tan) +* Redirect Output with subprocess if a token has a `>` sign (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/yosh/shell.py b/yosh/shell.py index e5f1832..c6b3055 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -19,14 +19,14 @@ def tokenize(string): # Find the `=` sign if el.find('=') != -1: if int(el.find('=')) > 0: - if int(el.find('=')) != len(token[i]): - if token[i][int(el.find('='))-1] != "=": - if token[i][int(el.find('='))+1] != "=": + if int(el.find('=')) != len(token[i]): + if token[i][int(el.find('='))-1] != "=": + if token[i][int(el.find('='))+1] != "=": token.append(str(token[i])) - token[i]="export" + token[i] = "export" break # Find the dollar sign - if el.startswith('$'): + elif el.startswith('$'): token[i] = str(os.getenv(token[i][1:])) break return token @@ -52,16 +52,26 @@ def execute(cmd_tokens): # Spawn a child process if platform.system() != "Windows": found = 0 + std = 0 # Auto find the command for i in os.getenv("PATH").split(":"): if os.path.exists(i + "/" + cmd_name): - # Fix the cmd_tokens - p = subprocess.Popen(cmd_tokens) - # Parent process read data from child process - # and wait for child process to exit - p.communicate() - found = 1 - break + for a, el in enumerate(cmd_tokens): + if el == ">": + with open(cmd_tokens[a+1], "a") as f: + f.flush() + # Fix the cmd_tokens + p = subprocess.Popen(cmd_tokens[0:a], stdout=f) + std = 1 + found = 1 + break + if std == 0: + p = subprocess.Popen(cmd_tokens) + # Parent process read data from child process + # and wait for child process to exit + p.communicate() + found = 1 + break if found == 0: for i in os.getenv("PATH").split(":"): for root, dirs, files in os.walk(i): From 6c808b9cfb219818455eae681fd792bdef3f7dbc Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 13:06:56 +0800 Subject: [PATCH 32/45] Run the command at the background if a token has a & sign --- CHANGES.md | 1 + yosh/shell.py | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f605532..c64bc80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ * Auto find the command if the command does not exit (Yuliang Tan) * Set an environment variable if a token has a `=` sign (Yuliang Tan) * Redirect Output with subprocess if a token has a `>` sign (Yuliang Tan) +* Run the command at the background if a token has a `&` sign (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/yosh/shell.py b/yosh/shell.py index c6b3055..fccafd2 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -53,11 +53,13 @@ def execute(cmd_tokens): if platform.system() != "Windows": found = 0 std = 0 + waitpid = 0 # Auto find the command for i in os.getenv("PATH").split(":"): if os.path.exists(i + "/" + cmd_name): for a, el in enumerate(cmd_tokens): - if el == ">": + # Find the '>' and '>>' sign + if el == ">" or el == ">>": with open(cmd_tokens[a+1], "a") as f: f.flush() # Fix the cmd_tokens @@ -65,11 +67,25 @@ def execute(cmd_tokens): std = 1 found = 1 break + # Find the '<' and '<<' sign + elif el == "<" or el == "<<": + with open(cmd_tokens[a+1], "r+") as g: + p = subprocess.Popen(cmd_tokens[0:a], stdin=g) + std = 1 + found = 1 + break + # Find the '&' sign,and run in the background + elif el == "&": + p = subprocess.Popen(cmd_tokens, + stdout=subprocess.PIPE) + waitpid = 1 + break if std == 0: - p = subprocess.Popen(cmd_tokens) - # Parent process read data from child process - # and wait for child process to exit - p.communicate() + if waitpid == 0: + p = subprocess.Popen(cmd_tokens) + # Parent process read data from child process + # and wait for child process to exit + p.communicate() found = 1 break if found == 0: From 53ae521961db14f27692456f4744eb0876c5bfa0 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 16:04:11 +0800 Subject: [PATCH 33/45] Auto find file if a token has a * sign --- CHANGES.md | 1 + yosh/shell.py | 54 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c64bc80..49297cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ * Set an environment variable if a token has a `=` sign (Yuliang Tan) * Redirect Output with subprocess if a token has a `>` sign (Yuliang Tan) * Run the command at the background if a token has a `&` sign (Yuliang Tan) +* Auto find file if a token has a `*` sign (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/yosh/shell.py b/yosh/shell.py index fccafd2..2502ec5 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -26,9 +26,45 @@ def tokenize(string): token[i] = "export" break # Find the dollar sign - elif el.startswith('$'): - token[i] = str(os.getenv(token[i][1:])) + if el.startswith('$'): + if int(el.find('/')) != -1: + strf = str(token[i][1:int(el.find('/'))]) + token[i] = str(os.getenv(token[i][1:int(el.find('/'))])) + str( + token[i][int(el.find('/')):len(token[i])]) + # Windows Support + elif int(el.find('\\')) != -1: + strf = str(token[i][1:int(el.find('\\'))]) + token[i] = str(os.getenv(token[i][1:int(el.find('\\'))])) + str( + token[i][int(el.find('\\')):len(token[i])]) + else: + token[i] = str(os.getenv(token[i][1:])) + break + # `*` sign does not support Windows + if platform.system() == "Windows": break + # Find the `*` sign + if int(el.find("*")) != -1: + if int(el.find("/")) == -1: + for files in os.listdir(os.getcwd()): + if int(files.find(el.strip("./*"))) != -1: + token[i] = os.getcwd() + "/" + files + break + elif int(el.find("./")) != -1: + for files in os.listdir(os.getcwd()): + if int(files.find(el.strip("./*"))) != -1: + token[i] = os.getcwd() + "/" + files + break + else: + found = 0 + for files in os.listdir(token[i][0:int(el.rfind('/'))]): + if found == 1: + break + if int(files.find(el.strip(token[i][0:int( + el.rfind('/'))]+"*"))) != -1: + token[i] = "/" + el.strip(token[i][int( + el.rfind('/')+1):len(el)]+"*") + "/" +files + found = 1 + break return token @@ -90,12 +126,11 @@ def execute(cmd_tokens): break if found == 0: for i in os.getenv("PATH").split(":"): - for root, dirs, files in os.walk(i): - for f in files: - if f.find(cmd_tokens[0]) != -1: - print "Do you mean: " + f - found = 1 - break + for files in os.listdir(i): + if files.find(cmd_tokens[0]) != -1: + print "Do you mean: " + files + found = 1 + break if found == 0: print "File Not Found" @@ -111,7 +146,7 @@ def execute(cmd_tokens): def shell_loop(): status = SHELL_STATUS_RUN - + his = open(os.getenv('HOME') + "/.shistory", "a") while status == SHELL_STATUS_RUN: # Display a command prompt if platform.system() != "Windows": @@ -148,6 +183,7 @@ def shell_loop(): try: # Tokenize the command input cmd_tokens = tokenize(cmd) + his.write(cmd) except: print("Error when receiving the command") # Fix a bug with inputing nothing From 297274a33459bac98e437425987d18ab662c91e5 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 17:00:43 +0800 Subject: [PATCH 34/45] add history builtin command --- CHANGES.md | 1 + yosh/builtins/__init__.py | 1 + yosh/builtins/history.py | 19 +++++++++++++++++++ yosh/shell.py | 27 ++++++++++++++------------- 4 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 yosh/builtins/history.py diff --git a/CHANGES.md b/CHANGES.md index 49297cf..172563d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ * Redirect Output with subprocess if a token has a `>` sign (Yuliang Tan) * Run the command at the background if a token has a `&` sign (Yuliang Tan) * Auto find file if a token has a `*` sign (Yuliang Tan) +* Add `history` built-in command (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/yosh/builtins/__init__.py b/yosh/builtins/__init__.py index d9f3722..0247463 100644 --- a/yosh/builtins/__init__.py +++ b/yosh/builtins/__init__.py @@ -2,3 +2,4 @@ from yosh.builtins.exit import exit from yosh.builtins.getenv import getenv from yosh.builtins.export import export +from yosh.builtins.history import history diff --git a/yosh/builtins/history.py b/yosh/builtins/history.py new file mode 100644 index 0000000..f03705e --- /dev/null +++ b/yosh/builtins/history.py @@ -0,0 +1,19 @@ +import os +from yosh.constants import * + + +def history(args): + count = 0 + if args: + count = int(args[0]) + 1 + file = open(os.getenv("HOME") + "/.shistory") + lines = file.readlines() + for i, el in enumerate(lines): + if count != 0: + if len(lines) - count < i < len(lines): + output = str(i) + " " + el + print (output.strip()) + else: + output = str(i) + " " + el + print (output.strip()) + return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index 2502ec5..b623235 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -20,8 +20,8 @@ def tokenize(string): if el.find('=') != -1: if int(el.find('=')) > 0: if int(el.find('=')) != len(token[i]): - if token[i][int(el.find('='))-1] != "=": - if token[i][int(el.find('='))+1] != "=": + if token[i][int(el.find('=')) - 1] != "=": + if token[i][int(el.find('=')) + 1] != "=": token.append(str(token[i])) token[i] = "export" break @@ -30,12 +30,12 @@ def tokenize(string): if int(el.find('/')) != -1: strf = str(token[i][1:int(el.find('/'))]) token[i] = str(os.getenv(token[i][1:int(el.find('/'))])) + str( - token[i][int(el.find('/')):len(token[i])]) + token[i][int(el.find('/')):len(token[i])]) # Windows Support elif int(el.find('\\')) != -1: strf = str(token[i][1:int(el.find('\\'))]) - token[i] = str(os.getenv(token[i][1:int(el.find('\\'))])) + str( - token[i][int(el.find('\\')):len(token[i])]) + token[i] = str(os.getenv(token[i][1:int(el.find('\\'))])) + \ + str(token[i][int(el.find('\\')):len(token[i])]) else: token[i] = str(os.getenv(token[i][1:])) break @@ -45,10 +45,10 @@ def tokenize(string): # Find the `*` sign if int(el.find("*")) != -1: if int(el.find("/")) == -1: - for files in os.listdir(os.getcwd()): - if int(files.find(el.strip("./*"))) != -1: - token[i] = os.getcwd() + "/" + files - break + for files in os.listdir(os.getcwd()): + if int(files.find(el.strip("./*"))) != -1: + token[i] = os.getcwd() + "/" + files + break elif int(el.find("./")) != -1: for files in os.listdir(os.getcwd()): if int(files.find(el.strip("./*"))) != -1: @@ -60,9 +60,9 @@ def tokenize(string): if found == 1: break if int(files.find(el.strip(token[i][0:int( - el.rfind('/'))]+"*"))) != -1: + el.rfind('/'))] + "*"))) != -1: token[i] = "/" + el.strip(token[i][int( - el.rfind('/')+1):len(el)]+"*") + "/" +files + el.rfind('/') + 1):len(el)] + "*") + "/" + files found = 1 break return token @@ -96,7 +96,7 @@ def execute(cmd_tokens): for a, el in enumerate(cmd_tokens): # Find the '>' and '>>' sign if el == ">" or el == ">>": - with open(cmd_tokens[a+1], "a") as f: + with open(cmd_tokens[a + 1], "a") as f: f.flush() # Fix the cmd_tokens p = subprocess.Popen(cmd_tokens[0:a], stdout=f) @@ -105,7 +105,7 @@ def execute(cmd_tokens): break # Find the '<' and '<<' sign elif el == "<" or el == "<<": - with open(cmd_tokens[a+1], "r+") as g: + with open(cmd_tokens[a + 1], "r+") as g: p = subprocess.Popen(cmd_tokens[0:a], stdin=g) std = 1 found = 1 @@ -206,6 +206,7 @@ def init(): register_command("exit", exit) register_command("getenv", getenv) register_command("export", export) + register_command("history", history) def main(): From ad3202157d85e84cd1c1954c118f00dadeac0ff9 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 17:06:31 +0800 Subject: [PATCH 35/45] Fix for travis --- yosh/builtins/history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yosh/builtins/history.py b/yosh/builtins/history.py index f03705e..7e1023d 100644 --- a/yosh/builtins/history.py +++ b/yosh/builtins/history.py @@ -12,8 +12,8 @@ def history(args): if count != 0: if len(lines) - count < i < len(lines): output = str(i) + " " + el - print (output.strip()) + print(output.strip()) else: output = str(i) + " " + el - print (output.strip()) + print(output.strip()) return SHELL_STATUS_RUN From ee0bbb43d2d054c837f002a1a6ca616346b50003 Mon Sep 17 00:00:00 2001 From: Ted Date: Tue, 2 Aug 2016 21:51:32 +0800 Subject: [PATCH 36/45] Fix some problems about the Code --- yosh/builtins/cd.py | 2 +- yosh/builtins/export.py | 2 +- yosh/builtins/getenv.py | 2 +- yosh/builtins/history.py | 28 +++++++----- yosh/shell.py | 95 ++++++++++++++++++++-------------------- 5 files changed, 67 insertions(+), 62 deletions(-) diff --git a/yosh/builtins/cd.py b/yosh/builtins/cd.py index 51e70b0..490a6b7 100644 --- a/yosh/builtins/cd.py +++ b/yosh/builtins/cd.py @@ -3,7 +3,7 @@ def cd(args): - if args: + if len(args) > 0: os.chdir(args[0]) else: os.chdir(os.getenv('HOME')) diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py index 0fe41b7..0e080f3 100644 --- a/yosh/builtins/export.py +++ b/yosh/builtins/export.py @@ -3,7 +3,7 @@ def export(args): - if args: + if len(args) > 0: var = args[0].split('=', 1) os.environ[var[0]] = var[1] return SHELL_STATUS_RUN diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py index 9e10164..a9358db 100644 --- a/yosh/builtins/getenv.py +++ b/yosh/builtins/getenv.py @@ -3,6 +3,6 @@ def getenv(args): - if args: + if len(args) > 0: print(os.getenv(args[0])) return SHELL_STATUS_RUN diff --git a/yosh/builtins/history.py b/yosh/builtins/history.py index 7e1023d..646ae19 100644 --- a/yosh/builtins/history.py +++ b/yosh/builtins/history.py @@ -3,17 +3,21 @@ def history(args): - count = 0 - if args: - count = int(args[0]) + 1 - file = open(os.getenv("HOME") + "/.shistory") - lines = file.readlines() - for i, el in enumerate(lines): - if count != 0: - if len(lines) - count < i < len(lines): - output = str(i) + " " + el + history_path = os.getenv('HOME') + '/.yosh_history' + + with open(history_path) as history_file: + lines = history_file.readlines() + # default limit is whole file + limit = len(lines) + + if len(args) > 0: + limit = int(args[0]) + + # start history line to print out + start = len(lines) - limit + + for line_num, line in enumerate(lines): + if line_num >= start: + output = str(line_num + 1) + " " + line print(output.strip()) - else: - output = str(i) + " " + el - print(output.strip()) return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index b623235..4fdf046 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -18,24 +18,24 @@ def tokenize(string): for i, el in enumerate(token): # Find the `=` sign if el.find('=') != -1: - if int(el.find('=')) > 0: - if int(el.find('=')) != len(token[i]): - if token[i][int(el.find('=')) - 1] != "=": - if token[i][int(el.find('=')) + 1] != "=": + if el.find('=') > 0: + if el.find('=') != len(el): + if token[i][el.find('=') - 1] != "=": + if token[i][el.find('=') + 1] != "=": token.append(str(token[i])) token[i] = "export" break # Find the dollar sign if el.startswith('$'): - if int(el.find('/')) != -1: - strf = str(token[i][1:int(el.find('/'))]) - token[i] = str(os.getenv(token[i][1:int(el.find('/'))])) + str( - token[i][int(el.find('/')):len(token[i])]) + if el.find('/') != -1: + strf = str(token[i][1:el.find('/')]) + token[i] = str(os.getenv(token[i][1:el.find('/')])) + str( + token[i][el.find('/'):len(token[i])]) # Windows Support - elif int(el.find('\\')) != -1: - strf = str(token[i][1:int(el.find('\\'))]) - token[i] = str(os.getenv(token[i][1:int(el.find('\\'))])) + \ - str(token[i][int(el.find('\\')):len(token[i])]) + elif el.find('\\') != -1: + strf = str(token[i][1:el.find('\\')]) + token[i] = str(os.getenv(token[i][1:el.find('\\')])) + \ + str(token[i][el.find('\\'):len(token[i])]) else: token[i] = str(os.getenv(token[i][1:])) break @@ -43,27 +43,27 @@ def tokenize(string): if platform.system() == "Windows": break # Find the `*` sign - if int(el.find("*")) != -1: - if int(el.find("/")) == -1: + if el.find("*") != -1: + if el.find("/") == -1: for files in os.listdir(os.getcwd()): - if int(files.find(el.strip("./*"))) != -1: + if files.find(el.strip("./*")) != -1: token[i] = os.getcwd() + "/" + files break - elif int(el.find("./")) != -1: + elif el.find("./") != -1: for files in os.listdir(os.getcwd()): - if int(files.find(el.strip("./*"))) != -1: + if files.find(el.strip("./*")) != -1: token[i] = os.getcwd() + "/" + files break else: - found = 0 - for files in os.listdir(token[i][0:int(el.rfind('/'))]): - if found == 1: + found = False + for files in os.listdir(token[i][0:el.rfind('/')]): + if found: break - if int(files.find(el.strip(token[i][0:int( - el.rfind('/'))] + "*"))) != -1: - token[i] = "/" + el.strip(token[i][int( - el.rfind('/') + 1):len(el)] + "*") + "/" + files - found = 1 + if files.find(el.strip( + token[i][0:el.rfind('/')] + "*")) != -1: + token[i] = "/" + el.strip(token[i][ + el.rfind('/') + 1:len(el)] + "*") + "/" + files + found = True break return token @@ -87,51 +87,52 @@ def execute(cmd_tokens): signal.signal(signal.SIGINT, handler_kill) # Spawn a child process if platform.system() != "Windows": - found = 0 - std = 0 - waitpid = 0 + found = False + # Std including "stdin" and "stdout" + std = False + waitpid = False # Auto find the command - for i in os.getenv("PATH").split(":"): - if os.path.exists(i + "/" + cmd_name): + for path in os.getenv("PATH").split(":"): + if os.path.exists(path + "/" + cmd_name): for a, el in enumerate(cmd_tokens): - # Find the '>' and '>>' sign - if el == ">" or el == ">>": + # Find the '>' + if el == ">": with open(cmd_tokens[a + 1], "a") as f: f.flush() # Fix the cmd_tokens p = subprocess.Popen(cmd_tokens[0:a], stdout=f) - std = 1 - found = 1 + std = True + found = True break - # Find the '<' and '<<' sign - elif el == "<" or el == "<<": + # Find the '<' + elif el == "<": with open(cmd_tokens[a + 1], "r+") as g: p = subprocess.Popen(cmd_tokens[0:a], stdin=g) - std = 1 - found = 1 + std = True + found = True break # Find the '&' sign,and run in the background elif el == "&": p = subprocess.Popen(cmd_tokens, stdout=subprocess.PIPE) - waitpid = 1 + waitpid = True break - if std == 0: - if waitpid == 0: + if not std: + if not waitpid: p = subprocess.Popen(cmd_tokens) # Parent process read data from child process # and wait for child process to exit p.communicate() - found = 1 + found = True break - if found == 0: + if not found: for i in os.getenv("PATH").split(":"): for files in os.listdir(i): if files.find(cmd_tokens[0]) != -1: print "Do you mean: " + files - found = 1 + found = False break - if found == 0: + if not found: print "File Not Found" else: @@ -146,7 +147,6 @@ def execute(cmd_tokens): def shell_loop(): status = SHELL_STATUS_RUN - his = open(os.getenv('HOME') + "/.shistory", "a") while status == SHELL_STATUS_RUN: # Display a command prompt if platform.system() != "Windows": @@ -183,7 +183,8 @@ def shell_loop(): try: # Tokenize the command input cmd_tokens = tokenize(cmd) - his.write(cmd) + with open(os.getenv('HOME') + "/.yosh_history", "a") as his_file: + his_file.write(cmd) except: print("Error when receiving the command") # Fix a bug with inputing nothing From 1b3eb15696aed7594d668ff4bd1c478c4dc3b5b7 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 16:13:21 +0700 Subject: [PATCH 37/45] Refactor finding the '=' sign --- yosh/shell.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 4fdf046..b0984ab 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -17,14 +17,13 @@ def tokenize(string): token = shlex.split(string) for i, el in enumerate(token): # Find the `=` sign - if el.find('=') != -1: - if el.find('=') > 0: - if el.find('=') != len(el): - if token[i][el.find('=') - 1] != "=": - if token[i][el.find('=') + 1] != "=": - token.append(str(token[i])) - token[i] = "export" - break + equal_pos = el.find('=') + if (equal_pos > 0 and + el[equal_pos - 1] != '=' and + el[equal_pos + 1] != '='): + token.append(str(token[i])) + token[i] = "export" + break # Find the dollar sign if el.startswith('$'): if el.find('/') != -1: From cdfcc820d61b198b68977fca437e34b044a09064 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 20:59:44 +0700 Subject: [PATCH 38/45] Revert to v0.2.0 Last merged PR contains at least a bug in 'export' command. However, the major issue is code is hard to read and maintain. We need to fix them first. --- CHANGES.md | 9 ---- README.md | 2 - yosh/builtins/__init__.py | 1 - yosh/builtins/cd.py | 2 +- yosh/builtins/export.py | 5 +- yosh/builtins/getenv.py | 3 +- yosh/shell.py | 105 +++----------------------------------- 7 files changed, 11 insertions(+), 116 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 172563d..e5e8b69 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,15 +5,6 @@ * Use subprocess module to fork and exec (Yuliang Tan) * Get an environment variable if a token begins with a dollar sign (Yuliang Tan) * Support Windows (Yuliang Tan) -* Fix command arguments are not passed to child process (Supasate Choochaisri) -* Add travis build to check PEP8 (Supasate Choochaisri) -* Add build badge (Supasate Choochaisri) -* Auto find the command if the command does not exit (Yuliang Tan) -* Set an environment variable if a token has a `=` sign (Yuliang Tan) -* Redirect Output with subprocess if a token has a `>` sign (Yuliang Tan) -* Run the command at the background if a token has a `&` sign (Yuliang Tan) -* Auto find file if a token has a `*` sign (Yuliang Tan) -* Add `history` built-in command (Yuliang Tan) 2016-07-08, Version 0.1.0 ========================= diff --git a/README.md b/README.md index c256072..2e6a3c8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # yosh - Your Own SHell -[![Build Status](https://travis-ci.org/supasate/yosh.svg?branch=master)](https://travis-ci.org/supasate/yosh) - A simple shell written in Python > Note: If you found this repo from the article **Create Your Own Shell in Python** [Part I](https://hackercollider.com/articles/2016/07/05/create-your-own-shell-in-python-part-1/) and [Part II](https://hackercollider.com/articles/2016/07/06/create-your-own-shell-in-python-part-2/), you can check out the `tutorial` branch for the source code used in the article. diff --git a/yosh/builtins/__init__.py b/yosh/builtins/__init__.py index 0247463..d9f3722 100644 --- a/yosh/builtins/__init__.py +++ b/yosh/builtins/__init__.py @@ -2,4 +2,3 @@ from yosh.builtins.exit import exit from yosh.builtins.getenv import getenv from yosh.builtins.export import export -from yosh.builtins.history import history diff --git a/yosh/builtins/cd.py b/yosh/builtins/cd.py index 490a6b7..51e70b0 100644 --- a/yosh/builtins/cd.py +++ b/yosh/builtins/cd.py @@ -3,7 +3,7 @@ def cd(args): - if len(args) > 0: + if args: os.chdir(args[0]) else: os.chdir(os.getenv('HOME')) diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py index 0e080f3..f416594 100644 --- a/yosh/builtins/export.py +++ b/yosh/builtins/export.py @@ -3,7 +3,6 @@ def export(args): - if len(args) > 0: - var = args[0].split('=', 1) - os.environ[var[0]] = var[1] + var = args[0].split('=', 1) + os.environ[var[0]] = var[1] return SHELL_STATUS_RUN diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py index a9358db..4f5f258 100644 --- a/yosh/builtins/getenv.py +++ b/yosh/builtins/getenv.py @@ -3,6 +3,5 @@ def getenv(args): - if len(args) > 0: - print(os.getenv(args[0])) + print(os.getenv(args[0])) return SHELL_STATUS_RUN diff --git a/yosh/shell.py b/yosh/shell.py index b0984ab..45f537e 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -16,54 +16,8 @@ def tokenize(string): token = shlex.split(string) for i, el in enumerate(token): - # Find the `=` sign - equal_pos = el.find('=') - if (equal_pos > 0 and - el[equal_pos - 1] != '=' and - el[equal_pos + 1] != '='): - token.append(str(token[i])) - token[i] = "export" - break - # Find the dollar sign if el.startswith('$'): - if el.find('/') != -1: - strf = str(token[i][1:el.find('/')]) - token[i] = str(os.getenv(token[i][1:el.find('/')])) + str( - token[i][el.find('/'):len(token[i])]) - # Windows Support - elif el.find('\\') != -1: - strf = str(token[i][1:el.find('\\')]) - token[i] = str(os.getenv(token[i][1:el.find('\\')])) + \ - str(token[i][el.find('\\'):len(token[i])]) - else: - token[i] = str(os.getenv(token[i][1:])) - break - # `*` sign does not support Windows - if platform.system() == "Windows": - break - # Find the `*` sign - if el.find("*") != -1: - if el.find("/") == -1: - for files in os.listdir(os.getcwd()): - if files.find(el.strip("./*")) != -1: - token[i] = os.getcwd() + "/" + files - break - elif el.find("./") != -1: - for files in os.listdir(os.getcwd()): - if files.find(el.strip("./*")) != -1: - token[i] = os.getcwd() + "/" + files - break - else: - found = False - for files in os.listdir(token[i][0:el.rfind('/')]): - if found: - break - if files.find(el.strip( - token[i][0:el.rfind('/')] + "*")) != -1: - token[i] = "/" + el.strip(token[i][ - el.rfind('/') + 1:len(el)] + "*") + "/" + files - found = True - break + token[i] = os.getenv(token[i][1:]) return token @@ -86,54 +40,11 @@ def execute(cmd_tokens): signal.signal(signal.SIGINT, handler_kill) # Spawn a child process if platform.system() != "Windows": - found = False - # Std including "stdin" and "stdout" - std = False - waitpid = False - # Auto find the command - for path in os.getenv("PATH").split(":"): - if os.path.exists(path + "/" + cmd_name): - for a, el in enumerate(cmd_tokens): - # Find the '>' - if el == ">": - with open(cmd_tokens[a + 1], "a") as f: - f.flush() - # Fix the cmd_tokens - p = subprocess.Popen(cmd_tokens[0:a], stdout=f) - std = True - found = True - break - # Find the '<' - elif el == "<": - with open(cmd_tokens[a + 1], "r+") as g: - p = subprocess.Popen(cmd_tokens[0:a], stdin=g) - std = True - found = True - break - # Find the '&' sign,and run in the background - elif el == "&": - p = subprocess.Popen(cmd_tokens, - stdout=subprocess.PIPE) - waitpid = True - break - if not std: - if not waitpid: - p = subprocess.Popen(cmd_tokens) - # Parent process read data from child process - # and wait for child process to exit - p.communicate() - found = True - break - if not found: - for i in os.getenv("PATH").split(":"): - for files in os.listdir(i): - if files.find(cmd_tokens[0]) != -1: - print "Do you mean: " + files - found = False - break - if not found: - print "File Not Found" - + # Unix support + p = subprocess.Popen(cmd_tokens) + # Parent process read data from child process + # and wait for child process to exit + p.communicate() else: # Windows support command = "" @@ -146,6 +57,7 @@ def execute(cmd_tokens): def shell_loop(): status = SHELL_STATUS_RUN + while status == SHELL_STATUS_RUN: # Display a command prompt if platform.system() != "Windows": @@ -182,8 +94,6 @@ def shell_loop(): try: # Tokenize the command input cmd_tokens = tokenize(cmd) - with open(os.getenv('HOME') + "/.yosh_history", "a") as his_file: - his_file.write(cmd) except: print("Error when receiving the command") # Fix a bug with inputing nothing @@ -206,7 +116,6 @@ def init(): register_command("exit", exit) register_command("getenv", getenv) register_command("export", export) - register_command("history", history) def main(): From e2c77d1269a4bf108b9ae0dee71ab6f563c62385 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 22:32:23 +0700 Subject: [PATCH 39/45] [Refactor] Display command prompt in more concise way --- yosh/shell.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 45f537e..0b7dedf 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -54,30 +54,30 @@ def execute(cmd_tokens): # Return status indicating to wait for next command in shell_loop return SHELL_STATUS_RUN +# Display a command prompt as `[@ ]$ ` +def display_cmd_prompt(): + # Get user and hostname + user = getpass.getuser() + hostname = socket.gethostname() + + # Get base directory (last part of the curent working directory path) + cwd = os.getcwd() + base_dir = os.path.basename(cwd) + + # Use ~ instead if a user is at his/her home directory + home_dir = os.path.expanduser('~') + if cwd == home_dir: + base_dir = '~' + + # Print out to console + sys.stdout.write("[%s@%s %s]$ " % (user, hostname, base_dir)) + sys.stdout.flush() def shell_loop(): status = SHELL_STATUS_RUN while status == SHELL_STATUS_RUN: - # Display a command prompt - if platform.system() != "Windows": - if os.getcwd() == os.getenv('HOME'): - dir = "~" - else: - dir = os.getcwd().split('/')[-1] - if os.geteuid() != 0: - sys.stdout.write( - '[' + getpass.getuser() + '@' + - socket.gethostname().split('.')[0] + - ' ' + dir + ']$ ') - else: - sys.stdout.write( - '[root@' + socket.gethostname().split('.')[0] + - ' ' + dir + ']# ') - else: - # Windows support - sys.stdout.write(os.getcwd() + "> ") - sys.stdout.flush() + display_cmd_prompt() # Do not receive Ctrl signal if platform.system() != "Windows": From f2a31302d8a725fae84e5b1efc7a8a2a4ffd231c Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 23:05:49 +0700 Subject: [PATCH 40/45] Add comment to ignore interrupts --- yosh/shell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yosh/shell.py b/yosh/shell.py index 0b7dedf..3b91623 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -79,9 +79,11 @@ def shell_loop(): while status == SHELL_STATUS_RUN: display_cmd_prompt() - # Do not receive Ctrl signal + # Ignore Ctrl-Z stop signal if platform.system() != "Windows": signal.signal(signal.SIGTSTP, signal.SIG_IGN) + + # Ignore Ctrl-C interrupt signal signal.signal(signal.SIGINT, signal.SIG_IGN) try: From 571d7f9869cb517802f5f9999190091f8aed72ad Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 23:11:09 +0700 Subject: [PATCH 41/45] Catch all errors with one try catch --- yosh/shell.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 3b91623..be939bf 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -54,6 +54,7 @@ def execute(cmd_tokens): # Return status indicating to wait for next command in shell_loop return SHELL_STATUS_RUN + # Display a command prompt as `[@ ]$ ` def display_cmd_prompt(): # Get user and hostname @@ -73,6 +74,7 @@ def display_cmd_prompt(): sys.stdout.write("[%s@%s %s]$ " % (user, hostname, base_dir)) sys.stdout.flush() + def shell_loop(): status = SHELL_STATUS_RUN @@ -82,27 +84,17 @@ def shell_loop(): # Ignore Ctrl-Z stop signal if platform.system() != "Windows": signal.signal(signal.SIGTSTP, signal.SIG_IGN) - # Ignore Ctrl-C interrupt signal signal.signal(signal.SIGINT, signal.SIG_IGN) try: # Read command input cmd = sys.stdin.readline() - except KeyboardInterrupt: - _, err, _ = sys.exc_info() - print(err) - - try: # Tokenize the command input cmd_tokens = tokenize(cmd) - except: - print("Error when receiving the command") - # Fix a bug with inputing nothing - try: # Execute the command and retrieve new status status = execute(cmd_tokens) - except OSError: + except: _, err, _ = sys.exc_info() print(err) From ee5491e01024b3adb96fe45bc38e7c2056f8479d Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 23:15:42 +0700 Subject: [PATCH 42/45] [Refactor] Move ignore signals into another function --- yosh/shell.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index be939bf..3f444b4 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -75,17 +75,22 @@ def display_cmd_prompt(): sys.stdout.flush() +def ignore_signals(): + # Ignore Ctrl-Z stop signal + if platform.system() != "Windows": + signal.signal(signal.SIGTSTP, signal.SIG_IGN) + # Ignore Ctrl-C interrupt signal + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def shell_loop(): status = SHELL_STATUS_RUN while status == SHELL_STATUS_RUN: display_cmd_prompt() - # Ignore Ctrl-Z stop signal - if platform.system() != "Windows": - signal.signal(signal.SIGTSTP, signal.SIG_IGN) - # Ignore Ctrl-C interrupt signal - signal.signal(signal.SIGINT, signal.SIG_IGN) + # Ignore Ctrl-Z and Ctrl-C signals + ignore_signals() try: # Read command input From 1b4f5ba7ff025b10531d86d96cb059eec463b7f4 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sat, 6 Aug 2016 23:41:02 +0700 Subject: [PATCH 43/45] Token does only one thing. Separate preprocessing into another function --- yosh/shell.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/yosh/shell.py b/yosh/shell.py index 3f444b4..c8d3f7c 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -14,11 +14,18 @@ def tokenize(string): - token = shlex.split(string) - for i, el in enumerate(token): - if el.startswith('$'): - token[i] = os.getenv(token[i][1:]) - return token + return shlex.split(string) + + +def preprocess(tokens): + processed_token = [] + for token in tokens: + # Convert $-prefixed token to value of an environment variable + if token.startswith('$'): + processed_token.append(os.getenv(token[1:])) + else: + processed_token.append(token) + return processed_token def handler_kill(signum, frame): @@ -97,6 +104,9 @@ def shell_loop(): cmd = sys.stdin.readline() # Tokenize the command input cmd_tokens = tokenize(cmd) + # Preprocess special tokens + # (e.g. convert $ into environment value) + cmd_tokens = preprocess(cmd_tokens) # Execute the command and retrieve new status status = execute(cmd_tokens) except: From 416fffcb15341f7d227c08979568b3f732aa73a4 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Sun, 7 Aug 2016 00:18:09 +0700 Subject: [PATCH 44/45] Add history built-in back --- yosh/builtins/__init__.py | 3 ++- yosh/builtins/history.py | 11 ++++++----- yosh/constants.py | 3 +++ yosh/shell.py | 6 +++++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/yosh/builtins/__init__.py b/yosh/builtins/__init__.py index d9f3722..b8b0169 100644 --- a/yosh/builtins/__init__.py +++ b/yosh/builtins/__init__.py @@ -1,4 +1,5 @@ from yosh.builtins.cd import cd from yosh.builtins.exit import exit -from yosh.builtins.getenv import getenv from yosh.builtins.export import export +from yosh.builtins.getenv import getenv +from yosh.builtins.history import history diff --git a/yosh/builtins/history.py b/yosh/builtins/history.py index 646ae19..42c1bb1 100644 --- a/yosh/builtins/history.py +++ b/yosh/builtins/history.py @@ -1,12 +1,12 @@ import os +import sys from yosh.constants import * def history(args): - history_path = os.getenv('HOME') + '/.yosh_history' - - with open(history_path) as history_file: + with open(HISTORY_PATH, 'r') as history_file: lines = history_file.readlines() + # default limit is whole file limit = len(lines) @@ -18,6 +18,7 @@ def history(args): for line_num, line in enumerate(lines): if line_num >= start: - output = str(line_num + 1) + " " + line - print(output.strip()) + sys.stdout.write('%d %s' % (line_num + 1, line)) + sys.stdout.flush() + return SHELL_STATUS_RUN diff --git a/yosh/constants.py b/yosh/constants.py index d7037b7..38da54a 100644 --- a/yosh/constants.py +++ b/yosh/constants.py @@ -1,2 +1,5 @@ +import os + SHELL_STATUS_STOP = 0 SHELL_STATUS_RUN = 1 +HISTORY_PATH = os.path.expanduser('~') + os.sep + '.yosh_history' diff --git a/yosh/shell.py b/yosh/shell.py index c8d3f7c..49f3468 100644 --- a/yosh/shell.py +++ b/yosh/shell.py @@ -33,6 +33,9 @@ def handler_kill(signum, frame): def execute(cmd_tokens): + with open(HISTORY_PATH, 'a') as history_file: + history_file.write(' '.join(cmd_tokens) + os.linesep) + if cmd_tokens: # Extract command name and arguments from tokens cmd_name = cmd_tokens[0] @@ -123,8 +126,9 @@ def register_command(name, func): def init(): register_command("cd", cd) register_command("exit", exit) - register_command("getenv", getenv) register_command("export", export) + register_command("getenv", getenv) + register_command("history", history) def main(): From 9a8efc48ad3bcde04119020fe05e1bba5c07b569 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Mon, 8 Aug 2016 03:59:26 +0700 Subject: [PATCH 45/45] Add guard condition back --- yosh/builtins/cd.py | 2 +- yosh/builtins/export.py | 5 +++-- yosh/builtins/getenv.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/yosh/builtins/cd.py b/yosh/builtins/cd.py index 51e70b0..490a6b7 100644 --- a/yosh/builtins/cd.py +++ b/yosh/builtins/cd.py @@ -3,7 +3,7 @@ def cd(args): - if args: + if len(args) > 0: os.chdir(args[0]) else: os.chdir(os.getenv('HOME')) diff --git a/yosh/builtins/export.py b/yosh/builtins/export.py index f416594..0e080f3 100644 --- a/yosh/builtins/export.py +++ b/yosh/builtins/export.py @@ -3,6 +3,7 @@ def export(args): - var = args[0].split('=', 1) - os.environ[var[0]] = var[1] + if len(args) > 0: + var = args[0].split('=', 1) + os.environ[var[0]] = var[1] return SHELL_STATUS_RUN diff --git a/yosh/builtins/getenv.py b/yosh/builtins/getenv.py index 4f5f258..a9358db 100644 --- a/yosh/builtins/getenv.py +++ b/yosh/builtins/getenv.py @@ -3,5 +3,6 @@ def getenv(args): - print(os.getenv(args[0])) + if len(args) > 0: + print(os.getenv(args[0])) return SHELL_STATUS_RUN