Skip to content

jeremymeadows/pyshell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PyShell

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

Running PyShell

git clone https://github.com/jeremymeadows/pyshell.git
cd pyshell
python -m pyshell

Shell Usage

Set environment variables

> # 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

Define aliases

> # 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

Use double/single quotes to control expansion

> 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

File input/output redirection

> echo foo > out.txt
> print("bar") >> out.txt
> cat < out.txt
foo
bar
> cat < out.txt > duplicate.txt

Command substitution

> 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

Variable expansion

> export NUMBER = 2
> pow($NUMBER, 7) - 1
127
>
> export NAME = "jeremy"
> print("hello $NAME")
hello jeremy

Pipelines

> cat file.txt | cowsay | lolcat
> ls ~ | grep filename

Job control

> vlc video.mp4
^Z
> jobs
[1]  stopped  vlc video.mp4
> bg
> jobs
[1]  running  vlc video.mp4
> disown
> jobs
> exit

Scripting

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.

Create shell commands

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

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

Custom tab completions

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)

Known Issues / In Development

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 but pwd | input() does not)

About

Python interpreter as a linux shell

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages