Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apc #8

Open
wants to merge 47 commits into
base: tutorial
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7294717
Fix ctrl+c but ctrl+z can't stop command
TeddyNight May 15, 2014
6e3e1aa
added the export command
TeddyNight May 15, 2014
4da449e
Add some features
invalid-email-address Jul 30, 2016
f4801d7
Fix bugs with Crtl+C
TeddyNight Jul 30, 2016
b629b7b
Fix bugs with some commands
TeddyNight Jul 30, 2016
c94084b
Added getenv and rewrite something
TeddyNight Jul 30, 2016
3cba775
get the
TeddyNight May 15, 2014
276aa5c
Use print() for compatibility with Python 2 and 3.
Aug 1, 2016
d71898b
Update the comment
Aug 1, 2016
6dd8713
Changes Readme.md
Aug 1, 2016
86b452b
[PEP8] Fix the code
TeddyNight Aug 1, 2016
1bcbf19
Fix the global variable and make the output beautiful
TeddyNight Aug 1, 2016
6d66ca2
[PEP8]Fix the code
TeddyNight Aug 1, 2016
1359c50
A bit change
TeddyNight Aug 1, 2016
36524db
Support for Windows
TeddyNight Aug 1, 2016
1385dd1
Support for windows
TeddyNight Aug 1, 2016
249e0d8
add setup.py
TeddyNight Aug 1, 2016
0c32aba
Fix a bug in cd with inputing nothing
TeddyNight Aug 1, 2016
2a6941f
Merge pull request #3 from YuliangTan/master
supasate Aug 1, 2016
67f7bf5
Fix PEP8
supasate Aug 1, 2016
02348f9
Fix author info
supasate Aug 1, 2016
81e747d
Reformat coding style
supasate Aug 1, 2016
135236c
Make code compatible with Python 3
supasate Aug 1, 2016
ad9bb47
Add changelog for 0.2.0
supasate Aug 1, 2016
197e42e
Update README about the tutorial branch
supasate Aug 1, 2016
3044fd5
Add travis to check PEP8
supasate Aug 1, 2016
48b7926
Run travis with various python versions
supasate Aug 1, 2016
85e4a88
[Fix] pass arguments to child process
supasate Aug 1, 2016
41087d5
Add build badge
supasate Aug 1, 2016
2a098c6
Auto find the command if the command does not exit
TeddyNight Aug 2, 2016
48523ba
Set an environment variable if a token has a sign
TeddyNight Aug 2, 2016
ffcd39e
Redirect Output with subprocess if a token has a > sign
TeddyNight Aug 2, 2016
6c808b9
Run the command at the background if a token has a & sign
TeddyNight Aug 2, 2016
53ae521
Auto find file if a token has a * sign
TeddyNight Aug 2, 2016
297274a
add history builtin command
TeddyNight Aug 2, 2016
ad32021
Fix for travis
TeddyNight Aug 2, 2016
ee0bbb4
Fix some problems about the Code
TeddyNight Aug 2, 2016
ea64106
Merge pull request #7 from YuliangTan/master
supasate Aug 6, 2016
1b3eb15
Refactor finding the '=' sign
supasate Aug 6, 2016
cdfcc82
Revert to v0.2.0
supasate Aug 6, 2016
e2c77d1
[Refactor] Display command prompt in more concise way
supasate Aug 6, 2016
f2a3130
Add comment to ignore interrupts
supasate Aug 6, 2016
571d7f9
Catch all errors with one try catch
supasate Aug 6, 2016
ee5491e
[Refactor] Move ignore signals into another function
supasate Aug 6, 2016
1b4f5ba
Token does only one thing. Separate preprocessing into another function
supasate Aug 6, 2016
416fffc
Add history built-in back
supasate Aug 6, 2016
9a8efc4
Add guard condition back
supasate Aug 7, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: python
python:
- 2.7
- 3.3
- 3.4
- 3.5

before_install:
- pip install pep8

script:
- find . -name \*.py -exec pep8 {} +
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 16 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python

from setuptools import setup

setup(name='yosh',
version='1.0',
description='Your own shell',
author='Supasate Choochaisri',
author_email='[email protected]',
url='https://github.com/supasate/yosh',
packages=['yosh', 'yosh.builtins'],
entry_points="""
[console_scripts]
yosh = yosh.shell:main
""",
)
3 changes: 3 additions & 0 deletions yosh/builtins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from yosh.builtins.cd import cd
from yosh.builtins.exit import exit
from yosh.builtins.export import export
from yosh.builtins.getenv import getenv
from yosh.builtins.history import history
5 changes: 4 additions & 1 deletion yosh/builtins/cd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@


def cd(args):
os.chdir(args[0])
if len(args) > 0:
os.chdir(args[0])
else:
os.chdir(os.getenv('HOME'))
return SHELL_STATUS_RUN
9 changes: 9 additions & 0 deletions yosh/builtins/export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os
from yosh.constants import *


def export(args):
if len(args) > 0:
var = args[0].split('=', 1)
os.environ[var[0]] = var[1]
return SHELL_STATUS_RUN
8 changes: 8 additions & 0 deletions yosh/builtins/getenv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
from yosh.constants import *


def getenv(args):
if len(args) > 0:
print(os.getenv(args[0]))
return SHELL_STATUS_RUN
24 changes: 24 additions & 0 deletions yosh/builtins/history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import sys
from yosh.constants import *


def history(args):
with open(HISTORY_PATH, 'r') 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:
sys.stdout.write('%d %s' % (line_num + 1, line))
sys.stdout.flush()

return SHELL_STATUS_RUN
3 changes: 3 additions & 0 deletions yosh/constants.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
import os

SHELL_STATUS_STOP = 0
SHELL_STATUS_RUN = 1
HISTORY_PATH = os.path.expanduser('~') + os.sep + '.yosh_history'
138 changes: 97 additions & 41 deletions yosh/shell.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import os
import sys
import shlex
import getpass
import socket
import signal
import subprocess
import platform
from yosh.constants import *
from yosh.builtins import *

Expand All @@ -12,56 +17,104 @@ def tokenize(string):
return shlex.split(string)


def execute(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)

# 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
os.execvp(cmd_tokens[0], cmd_tokens)
elif pid > 0:
# Parent process
while True:
# Wait response status from its child process (identified with pid)
wpid, status = os.waitpid(pid, 0)

# Finish waiting if its child process exits normally or is
# terminated by a signal
if os.WIFEXITED(status) or os.WIFSIGNALED(status):
break
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):
raise OSError("Killed!")


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]
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)

# Wait for a kill signal
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()
else:
# Windows support
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


def shell_loop():
status = SHELL_STATUS_RUN
# Display a command prompt as `[<user>@<hostname> <dir>]$ `
def display_cmd_prompt():
# Get user and hostname
user = getpass.getuser()
hostname = socket.gethostname()

while status == SHELL_STATUS_RUN:
# Display a command prompt
sys.stdout.write('> ')
sys.stdout.flush()
# Get base directory (last part of the curent working directory path)
cwd = os.getcwd()
base_dir = os.path.basename(cwd)

# Read command input
cmd = sys.stdin.readline()
# Use ~ instead if a user is at his/her home directory
home_dir = os.path.expanduser('~')
if cwd == home_dir:
base_dir = '~'

# Tokenize the command input
cmd_tokens = tokenize(cmd)
# Print out to console
sys.stdout.write("[%s@%s %s]$ " % (user, hostname, base_dir))
sys.stdout.flush()

# Execute the command and retrieve new status
status = execute(cmd_tokens)

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 and Ctrl-C signals
ignore_signals()

try:
# Read command input
cmd = sys.stdin.readline()
# Tokenize the command input
cmd_tokens = tokenize(cmd)
# Preprocess special tokens
# (e.g. convert $<env> into environment value)
cmd_tokens = preprocess(cmd_tokens)
# Execute the command and retrieve new status
status = execute(cmd_tokens)
except:
_, err, _ = sys.exc_info()
print(err)


# Register a built-in function to built-in command hash map
Expand All @@ -73,6 +126,9 @@ def register_command(name, func):
def init():
register_command("cd", cd)
register_command("exit", exit)
register_command("export", export)
register_command("getenv", getenv)
register_command("history", history)


def main():
Expand Down