Package: pieshell
AboutPieshell is a Python shell environment that combines the
expressiveness of shell pipelines with the power of python iterators.It can be used in two major ways:As an interactive shell replacing e.g. bashAs an ordinary python module replacing e.g. subprocess.PopenTable of contentsInstallationAs a shellExecuting basic commandsFull syntax for command linesRedirectsInterfacing between python functions and shell
commandsEnvironment variablesArgument expansionProcessesProcess browsingJob controlError handlingBashsourceAsynchronous IOAs a python moduleEnvironment variablesArgument expansionPysh modulesPysh functionsEval and execConfigurationChanging the promptBuiltinsExternal toolsTips and tricksUnit testsCopyrightInstallation$ pip install pieshellAs a shellExecuting basic commandsTo start pieshell in interactive mode, just run the command pieshell:$ pieshellThe interactive pieshell environment supports all normal python syntax.140:/home/redhog/Projects/beta/pieshell >>> print 3+4
7In addition, you can run programs just like in any shell by writing
their names140:/home/redhog/Projects/beta/pieshell >>> ls
build deps dist LICENSE.txt pieshell pieshell.egg-info README.md
setup.pyParameters to programs however have to be given as proper python strings
within parenthesis, like a python function call140:/home/redhog/Projects/beta/pieshell >>> ls("-a")
. .. build deps dist .git .gitignore LICENSE.txt pieshell
pieshell.egg-info .#README.md README.md setup.pyPiping the standard output of one command to the standard input of
another works just like in bash140:/home/redhog/Projects/beta/pieshell >>> ls("-a") | grep("-e", ".py")
setup.pyChanging directory is done using the command cd:140:/home/redhog/Projects/beta/pieshell >>> cd("..")
140:/home/redhog/Projects/beta >>>Full syntax for command linesTo execute commands that require a path, for example ones in the
current directory, or commands with a dot in their names140:/home/redhog/Projects/beta/pieshell >>> _("./setup.py", "--help")
Common commands: (see '--help-commands' for more)
...The underscore represents the virtual root command that has no
parameters, not even a command name. In general, there are two
equivalent syntaxes for parameters: as function parameter strings, and
as attribute names. The two syntaxes can be mixed freely. All of the
following are equivalent:_("foo", "bar", "fie")
_.foo("bar", "fie")
_.foo.bar.fie()
foo.bar.fie()
foo.bar.fieExample usage:git.diff("-U")In addition to these two generic syntaxes, there are two more
specialized syntaxes for options:The function call syntax also supports named parameters, which are
converted into "--name=value" pairs. Note that the order can not be
guaranteed as named parameters are sent around as dictionaries inside
python:git.diff(unified=4)Short options, like-Uabove do not actually need quotes, and can be
specified inside or outside the function call syntax:git.diff(-U, -w)
git.diff-U-wNotes for programs with wierd parameter syntax, likefind:finddoes not use the standard double minus (--) before long options, and
takes the option value as a separate argument, rather than separating
the name and value with=like most programs. The above special
syntax for short options actually cover this use case too:find(".", -name, "*.py")RedirectsStandard out and standard in of a pipeline can be redirected to a file
by piping to or from a string (the filename). As a special case, None
is a short hand for "/dev/null"140:/home/redhog/Projects/beta/pieshell >>> ls | "foo"

140:/home/redhog/Projects/beta/pieshell >>> "foo" | cat
bar
build
deps
dist
foo
LICENSE.txt
pieshell
pieshell.egg-info
README.md
setup.py

140:/home/redhog/Projects/beta/pieshell >>> ls | NoneRedirects can also be made with a more explicit syntax that allows
redirecting other file descriptors than stdin and stdout:139:/home/redhog/Projects/beta/pieshell >>> cat |
  Redirect("stdin", "foo") | Redirect("stdout", "bar")The constructor for redirect takes the following arguments:Redirect(fd, source, flag=None, mode=0777)fdcan be either an int, or one of"stdin","stdout"and"stderr".sourceis either a string filename, an int file descriptor or one of
the special valuesPIPE,TMPandSTRING.PIPEcreate a new
pipe which you are required to connect up manually.TMPgenerates a
temporary named file. The filename is available inRunningPipeline.processes[].output_files[filedescriptornr].path.STRINGgenerates a temporary named file, but reads and deletes it
when the pipeline exists, putting its content inRunningPipeline.processes[].output_content[filedescriptornr].flagandmodehave the same semantics as foros.open(). Flags do
not have to be given forstdin,stdoutandstderr/ fd0,1and2and defaults toos.O_RDONLYoros.O_RDONLY | os.O_CREAT.Interfacing between python functions and shell commandsShell commands are first class python objects, and their input and
output can be interacted with easily from python in the form of
iterators. Iterating over a shell command iterates over the lines of
its standard out140:/home/redhog/Projects/beta/pieshell >>> list(ls("-a"))
['.', '..', 'build', 'deps', 'dist', '.git', '.gitignore',
 'LICENSE.txt', 'pieshell', 'pieshell.egg-info', '.#README.md',
 'README.md', 'setup.py']
140:/home/redhog/Projects/beta/pieshell >>> for x in ls("-a"):
...   if x.endswith('.py'):
...      print x
... 
setup.pyPiping an iterator into a shell command, sends its items as lines to
the standard in of the shell command140:/home/redhog/Projects/beta/pieshell >>> list(["foo", "bar.py", "fie.py"] |
  grep("-e", ".py"))
['bar.py', 'fie.py']
140:/home/redhog/Projects/beta/pieshell >>> def foo():
...     yield "hello"
...     yield "world"
140:/home/redhog/Projects/beta/pieshell >>> foo() | cat
hello
worldIn addtion, iterators and pipelines may be used as arguments to
commands and will be seen by the command as a filename, which when
opened and read from will produce the output of that iterator as
lines, or the standard output of the pipeline.140:/home/redhog/Projects/beta/pieshell >>> list(cat(["foo", "bar"]))
['foo', 'bar']
140:/home/redhog/Projects/beta/pieshell >>> list(cat(["foo", "bar"] | cat))
['foo', 'bar']Environment variablesEnvironment variables are available directly in the shell as
variables, together with any local python variables. In addition, they
are available in the dictionary exports.140:/home/redhog/Projects/beta/pieshell >>> LANG
'en_US.UTF-8'Assigning to the name of an already exported environment variable
updates the value of that variable.140:/home/redhog/Projects/beta/pieshell >>> LANG = "sv_SE.UTF-8"
140:/home/redhog/Projects/beta/pieshell >>> exports["LANG"]
'sv_SE.UTF-8'Assigning to a variable name not already used as an environment
variable creates a local python variable.140:/home/redhog/Projects/beta/pieshell >>> foo = "hello"
140:/home/redhog/Projects/beta/pieshell >>> "foo" in exports
False
140:/home/redhog/Projects/beta/pieshell >>> foo
'hello'To export a new variable, you have to assign it in the exports
dictionary.140:/home/redhog/Projects/beta/pieshell >>> exports["bar"] = "world"
140:/home/redhog/Projects/beta/pieshell >>> bar
'world'Argument expansionAll parameter strings in commands are subject to expansion unless
wrapped in a call to R(), e.g. R("my * string * here")."~" and "~username" are expanded using os.path.expanduser()Variable expansion is done using the python % operator on python
variables as well as environment variables.Pattern matching is done using glob.glob()ProcessesA running pipeline is represented by a RunningPipeline instance. This
object is returned by the Pipeline.run() and
Pipeline.run_interactive() methods. In interactive shell mode the
RunningPipeline instance for the last executed pipeline is available
in the last_pipeline variable.A RunningPipeline instance can be used to extract events and statuses
of the processes involved in the pipeline:RunningPipeline.processes is a list of RunningItem instances, each
representing an external process or a python function.RunningPipeline.failed_processes is a list of RunningItem instances
for those processes in the pipeline that have failed (returned a
non-zero exit status).RunningPipeline.pipeline is a (deep) copy of the original pipeline
object, with additional run status added, e.g. links to processes,
exit status etc.RunningPipeline.wait() waits for all processes in the pipeline to
terminate.As a convenience, RunningItem:s can be retrieved by the name of their
corresponding binary as properties of the RunningPipeline. E.g.p = ls | grep(-e, ".py")
p.grepA RunningItem instance represents an external process or a python
function:RunningItem.cmd points to the part of the
RunningPipeline.pipeline structure that gave rise to this process.RunningItem.is_running is True if the process is still
running.RunningItem.is_failed is True if the process has failed somehow
(process with non-zero exit status, function threw an exception).RunningItem.output_content contains a dictionary of the output of
any STRING redirection for the process with the file descriptors as
keys.RunningProcess.iohandler.last_event contains a dictionary of the
members of the last event from the process. The members have the
same names and meaning as the members of the signalfd_siginfo
struct, see "man signalfd" for details.Process browsingThis functionality is only available ifpsutilis installed.The classPstreeProcessrepresents any process not directly started
by Pieshell.RunningPipeline,RunningItem, andPstreeProcessall exposes a
unified interface where child processes, parent processes, group
leaders and session leaders are available as properties. Child
porcesses uses the names of their binaries as property names. Parent
processes, session and group leaders usePARENT,SESSandGROUPrespectively.RunningItem.details, andPstreeProcess.detailscontains apsutil.Process()instance, and most of the members and methods of
that instance are directly available on theRunningItemorPstreeProcessobject.To access aPstreeProcessobject for the current shell and for the
init process (pid 1), there are two global variablesINITandCURRENT.The currently logged in users and their login session processes can be
accessed using the globalUSERSvariable with a similar semantic for
properties.All these objects support tab completion to let you easily naviage the
tree of running porcesses on your system.redhog@glittertind:~/pieshell[master] >>> USERS.redhog.localhost.mate_panel.mate_terminal.bash.bin_bash.bash.pid_10171.python3_9
_('/home/redhog/pieshell/env/bin/python3.9', '/home/redhog/pieshell/env/bin/pieshell') as 26341

redhog@glittertind:~/pieshell[master] >>> CURRENT
_('/home/redhog/pieshell/env/bin/python3.9', '/home/redhog/pieshell/env/bin/pieshell') as 26341Job controlA pipeline can be started in the background by appending&True, or&Noneto do the same and also redirect stdout to /dev/null.last_pipelinecan be used to access the backgrounded pipeline.A running pipeline can be stopped by hittingCTRL-Z. A stopped
pipeline can be restarted in the background with any ofbg
bg(last_pipeline)
last_pipeline.restart()or in the foreground withfg
fg(last_pipeline)
last_pipeline.wait()Error handlingWhen a pipeline fails, e.g. by one of the involved processes exiting
with a non-zero status, RunningPipeline.wait() and
Pipeline.run_interactive() will throw a PipelineFailed exception after
all processes have exited.PipelineFailed.pipeline holds a reference to the RunningPipeline
instance that generated the exception.If a pipeline is interrupted with CTRL-C, a PipelineInterrupted is
raised.PipelineInterrupted.pipeline holds a reference to the
RunningPipeline instance.If you want to catch errors in a script, you can use normal Python
exception handling:try:
except PipelineFailed as e:
    e.pipeline.failed_processes[0].pipelineBashsourceBash provides the commandsourceto run the content of a bash script inside the current shell,
effectively letting an external script update the environment variables of the running shell.
This functionality is often used for setting up local development environments, likevirtualenv.Pieshell provides a builtin to emulate this functionality, with bash scripts:>>> bashsource("myscript.sh")will runmyscript.shin a bash shell followed bydeclare -x. It parses the output ofdeclare -xand updatesexportsaccordingly. As a special case>>> bashsource()will do the same, but without running any script first, esentially
just using whatever variables are set up by your.bashrcor.profile.Asynchronous IOAll constructs described above to use iterators, can equally well be
used as / with async iterators. Pipelines can be awaited, which runs
them and waits for them to exit. RunningPipelines can be awaited,
which just waits for them to exit.As a python module>>> from pieshell import *All functionality available in the interactive shell is available when
using pieshell as an ordinary python module. However, a slighly more
cumbersome syntax is required.In particular, shell commands can not be run just by writing their
names. Instead, they have to be accessed as members of the "env"
object:>>> list(env.ls("-a") | env.grep("-e", "io"))
["iterio.py", "iterio.pyc"]Commands are also not run with standard out to the screen when simply
printed using the repr() function but must instead be used as
iterators as is done above using the list() function.A pipeline can be run in the background (with input/output to the screen) using>>>> status = ~(env.ls("-a") | env.grep("-e", "io"))or in the forground (waiting until it exists)>>>> status = +(env.ls("-a") | env.grep("-e", "io"))The env object holds the current working directory, which can be changed with>>> env.cd("..")You can also create multiple environments and use them
siumultaneously, even within the same pipeline>>> env2 = env()
>>> env2.cd("somedir")Environment variablesEnvironment variables are available as a dictionary in env._exports.Argument expansionVariable expansion is only done on environment variables, as there is
no way for pieshell to find out about the right scope to do variable
lookups in in any given situation.Pysh modulesIn addition to being able to use pieshell code in ordinary python
modules using this slightly more verbose syntax, pieshell supports
importing modules named modulename.pysh rather than modulename.py.
Pysh modules support the full syntax of the interactive pieshell
console. Pysh modules can be imported using the standard import syntax
as soon as pieshell itself has been imported, and from the interactive
pieshell.Be aware that you won't get aNameErrorfor misspelled variable
names in pyshmodules, but instead apieshell.pipeline.running.PipelineFailederror, or if not used in a
way that will cause it to run, just a pipeline object stored somewhere
it shouldn't.Pysh functionsSometimes you don't want to extract a bunch of functions to a separate
pysh module, but you'd still like to be able to use the simpler syntax
in them. That's still possible usig the@pyshfunctiondecorator:>>> @pieshell.env.pyshfunction
>>> def foo():
...    return list(echo("hello"))You can use any environment for this, not only the default one like in
the example above.Eval and execBy giving an instance ofpieshell.environ.EnvScopewith an environment
set to interactive asglobalsargument to eitherevalorexec, the
full syntax of Pieshell can be used in the string to evaluate/execute.>>> scope = pieshell.environ.EnvScope(env=pieshell.env(interactive=True))
>>> eval("ls()", scope)
build deps dist LICENSE.txt pieshell pieshell.egg-info README.md
setup.pyConfigurationWhen running pieshell in interactive mode it executes~/.config/pieshellat startup if it exists. This file can be used to
configure the interactive environment the same way~/.bashrccan be
used to configure the bash shell. For example it can be used to load
python modules, execute shell pipelines or set environment variables.
An example config file is supplied inpieshell/resources/default_config.pysh.Changing the promptThe prompt that Pieshell uses is generated by the methodpieshell.Environment.__str__. This method can be overridden by
assigning a new function to it:def prompt(self):
    return "Pieshell> "Be careful if you run pipelines or other code inside the prompt that
might call repr() as that might lead to an infinite loop of prompt
printing. To solve this, use a thread local variable as a flag to
catch and break any recursion.BuiltinsWhile pieshell lets you pipe to and from ordinary python functions,
they don't offer the same syntax and tab-completion as external
commands (e.g. 'myfunction.arg1.arg2(name=value)'), they can't modify
the environment or do fancy redirects. Builtin commands provide all of
this, at the cost of a slightly clumsier syntax:class MyMagicBuiltin(pieshell.Builtin):
    """More magic to the people
    """
    name = "magic"

    def _run(self, redirects, sess, indentation = ""):
        # redirects is an instance of pieshell.Redirects
        #
        # sess is an opaque data structure that must be passed to
        # any call to _run() you do yourself from this method (or
        # any function it calls).
        #
        # indentation is a string containing only whitespace, to
        # be prepended to any debug printing lines you print.
        #
        # Returns a list of instances of some pieshell.RunningItem
        # subclass

        self._cmd = self._env.find(
            ".", "-name", "%s.txt" % self._arg[1]) | self._env.tac
        return self._cmd._run(redirects, sess, indentation)


    # Optional for tab completion
    def __dir__(self):
        return ["light", "dark"]Builtins need to be registered to be available in the shell. This can
be done either from python withpipeline.BuiltinRegistry.register(MyMagicBuiltin)or from setup.py in any package:setup(
    entry_points={
        'pieshell.builtin': [
            "magic = mypackage:MyMagicBuiltin",
        ]
    }
)External toolsA short list of tools that might be usefull together with this project:psutil- python api for getting ps / top style information about a processReRedirect- redirect io for an already running processReptyr- move a running process to a new controlling terminalDeptyr- forward output for a new process to another controlling terminalTips and tricksThepandas reader for a table of fixed-width formatted
linesdoes a pretty good job at parsing the output from several standard
command line tools such as ls(-l):>>> pd.read_fwf(io.StringIO(str(ls(-l))))
     total 328  Unnamed: 1 Unnamed: 2 Unnamed: 3  Unnamed: 4 Unnamed: 5  Unnamed: 6 Unnamed: 7         Unnamed: 8
0   drwxr-xr-x           6     redhog     redhog        4096       okt.          16      21:45              build
1   -rw-rw-r--           1     redhog     redhog          25       jan.          11       2021        _config.yml
2   drwxrwxr-x           2     redhog     redhog        4096       okt.          25      21:30               dist
3

>>> pd.read_fwf(io.StringIO(str(docker.images)))
   REPOSITORY     TAG      IMAGE ID        CREATED    SIZE
0      python       3  a8405b7e74cf   7 months ago   921MB
1      ubuntu   22.04  08d22c0ceb15   7 months ago  77.8MB
2      ubuntu  latest  d13c942271d6  21 months ago  72.8MBUnit testsTo run the unit- and integration testspip install nose2
nose2 -s testsCopyrightPieshell copyright 2016 Egil Mölleregil.moller@piratpartiet.sePieshell is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.You should have received a copy of the GNU Lesser General Public
License along with this program. If not, seehttp://www.gnu.org/licenses/.