PyShell is a simple shell built on top of the Python interpreter. Any valid Python is also valid PyShell, and it aims to implement all the important shell features plus some extra useful ones:
- custom commands
- completion
- file input/output redirection
- command substitution
- pipelines
- read shell variables from Python
- run shell commands from a Python function
- background jobs / process management
git clone https://github.com/jeremymeadows/pyshell.git
cd pyshell
python -m pyshell
> # using Python `environ` dict
> import os
> os.environ["ENV_VAR1"] = "value"
> # exporting new variable (creates Python variable ENV_VAR2 and adds to `environ`)
> export ENV_VAR2 = "second variable"
> # exporting existing variable
> ENV_VAR3 = 7
> export ENV_VAR3
>
> echo $ENV_VAR1 $ENV_VAR2 $ENV_VAR3
value second variable 7
> # quotes are optional unless they are used to preserve spaces, like in filenames, for example
> alias ls = ls --color
> alias proj = cd "~/Projects/project with space/"
> alias f = fortune | cowsay | lolcat
> alias p = print("python in the shell")
> p
python in the shell
> export DIR = "foo bar"
> ls
bar/ foo/ 'foo bar'/
> ls $DIR
'foo bar':
> ls "$DIR"
'foo bar':
> ls '$DIR'
ls: cannot access '$DIR': No such file or directory
> echo foo > out.txt
> print("bar") >> out.txt
> cat < out.txt
foo
bar
> cat < out.txt > duplicate.txt
> ls $(os.getenv("HOME"))
Desktop Downloads Documents Music Pictures Video
> readlink $(which pysh)
/home/jeremy/Projects/pyshell/pysh
> file_name = "loremipsum.txt"
> echo $(open(file_name).readlines()[0].upper())
LOREM IPSUM DOLOR SIT AMET
> export NUMBER = 2
> pow($NUMBER, 7) - 1
127
>
> export NAME = "jeremy"
> print("hello $NAME")
hello jeremy
> cat file.txt | cowsay | lolcat
> ls ~ | grep filename
> vlc video.mp4
^Z
> jobs
[1] stopped vlc video.mp4
> bg
> jobs
[1] running vlc video.mp4
> disown
> jobs
> exit
PyShell can interpret any valid Python code or shell commands.
It also supports creating custom shell commands from a Python function which can be loaded into the current enviroment using the source
command.
By default, ~/.pyshrc
is sourced when the shell starts, and that file can be used to customize/configure PyShell.
Add the command
decorator to a function to give it a name and allow it to be called as a shell command.
from pyshell.commands import command
@command("myfunc")
def _myfunc(*args):
print("I was passed", args)
> myfunc 1 2 3
I was passed ('1', '2', '3')
Command substitution can also be used in scripts to run shell commands. Strings and string lists are both valid.
from pyshell.commands import command
@command("rcow")
def _rainbowcow_command(*args):
"""A command that prints a rainbow cow talking"""
if not args:
$("cowsay Moo | lolcat")
else:
$(["cowsay", *args, "| lolcat"])
A dictionary can be registered with the completer which has a prefix as the key.
If the current text matches the prefix then the value array will be presented as suggestions for the next argument.
Dynamic entries will run a function when tab
is pressed, and the returned list will be offered as suggestions.
from pyshell.complete import completer
entries = {
"docker": ["build", "run", "exec", "ps", "image", "pull", "push", "logs", "rm"],
"docker image": ["build", "inspect", "ls", "prune", "rm", "save", "tag"],
}
try:
# if the docker package is installed, add dynamic completions for the container names
import docker
def container_names():
client = docker.from_env()
return [c.name for c in client.containers.list(all=True)]
completer.register_dynamic("docker rm", container_names)
except ModuleNotFoundError:
pass
completer.register(entries)
One goal is for PyShell to be intuitive as a combination of Python and shell scripting, so if something doesn't work but feels like it should it can probably be treated as a bug.
- bitwise OR does not work (
2 | 3
) because it is treated as pipe - does not yet handle multiline input
export
should also evaluate expressions/pipelines if given- can only pipe out of Python, not into (eg.
print("hello world") | lolcat
works butpwd | input()
does not)