Metadata-Version: 2.1
Name: citizenshell
Version: 2.2.4
Summary: Interact with shell locally or over different connection types (telnet, ssh, serial, adb)
Home-page: https://github.com/meuter/citizenshell
Author: Cedric Meuter
Author-email: cedric.meuter@gmail.com
License: MIT
Download-URL: https://github.com/meuter/citizenshell/archive/2.2.4.tar.gz
Keywords: shell,telnet,adb,ssh,serial
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Utilities
Requires-Dist: termcolor (>=1.1.0)
Requires-Dist: paramiko (>=2.4.0)
Requires-Dist: uritools (>=2.1.0)
Requires-Dist: pyserial (>=3.4)
Requires-Dist: scp (>=0.10.2backport.tempfile>=0.4.0)

citizenshell |Build Status|
===========================

**citizenshell** is a python library allowing to execute shell commands
either locally or remotely over several protocols (telnet, ssh, serial
or adb) using a simple and consistent API. This library is compatible
with both python 2 (2.7) and 3 (>=3.4) as well as with
`PyPy <https://pypy.org/>`__. For now, it focuses on POSIX platforms
like Linux and MacOS, but may be extended to work to Windows based
platform in the future. It is distributed under
`MIT <https://opensource.org/licenses/MIT>`__ license.

Installation
------------

**citizenshell** can simply installed using ``pip install citizenshell``

Obtaining a shell
-----------------

First you need a shell. For that you have several options:

1. use the built-in ``LocalShell`` for quick access:

   .. code:: python

       from citizenshell import sh

2. you can instanciate your own ``LocalShell``:

   .. code:: python

       from citizenshell import LocalShell

       shell = LocalShell()

3. you can instanciate the ``TelnetShell`` for shell over telnet:

   .. code:: python

       from citizenshell import TelnetShell

       shell = TelnetShell(hostname="acme.org", username="john",
                           password="secretpassword")

4. you can instanciate the ``SecureShell`` for shell over SSH:

   .. code:: python

       from citizenshell import SecureShell

       shell = SecureShell(hostname="acme.org", username="john",
                           password="secretpassword")

5. you can instanciate the ``AdbShell`` for shell over ADB:

   .. code:: python

       from citizenshell import AdbShell

       shell = AdbShell(hostname="acme.org", username="john",
                        password="secretpassword")

6. you can instanciate the ``SerialShell`` for shell over serial line:

   .. code:: python

       from serial import EIGHTBITS, PARITY_NONE
       from citizenshell import SerialShell

       shell = SerialShell(port="/dev/ttyUSB3", username="john",
                           password="secretpassword",
                           baudrate=115200, parity=PARITY_NONE, bytesize=EIGHTBITS)

7. you can also obtain shell objects by URI using the ``Shell``
   function:

   .. code:: python

       from citizenshell import Shell

       localshell = Shell()
       telnetshell = Shell("telnet://john:secretpassword@acme.org:1234")
       secureshell = Shell("ssh://john:secretpassword@acme.org:1234")
       adbshell = Shell("adb://myandroiddevice:5555")
       serialshell = Shell("serial://jogn:secretpassword@/dev/ttyUSB3?baudrate=115200")

   you can also mix and match betweens providing arguments in the URI or
   via kwargs:

   .. code:: python

       telnetshell = Shell("telnet://john@acme.org", password="secretpassword", port=1234)
       serialshell = Shell("serial://john:secretpassword@/dev/ttyUSB3", baudrate=115200)

Using a shell
-------------

Once you have shell, any shell, you can call it directly and get the
standart output:

.. code:: python

    assert shell("echo Hello World") == "Hello World"

You can also iterate over the standard output:

.. code:: python

    result = [int(x) for x in shell("""
        for i in 1 2 3 4; do
            echo $i;
        done
    """)]
    assert result == [1, 2, 3, 4]

You don't have to wait for the command to finish to receive the output.

This loop

.. code:: python

    for line in shell("for i in 1 2 3 4; do echo -n 'It is '; date +%H:%M:%S; sleep 1; done", wait=False)
        print ">>>", line + "!"

would produce something like:

.. code:: text

    >>> It is 14:24:52!
    >>> It is 14:24:53!
    >>> It is 14:24:54!
    >>> It is 14:24:55!

You can extract stdout, stderr and exit code seperately:

.. code:: python

    result = shell(">&2 echo error && echo output && exit 13")
    assert result.stdout() == ["output"]
    assert result.stderr() == ["error"]
    assert result.exit_code() == 13

You can inject environment variable to the shell

.. code:: python

    assert shell("echo $VAR", VAR="bar") == "bar"

By default, shell inherits "$CWD" from the environment (aka $PWD).

Still, if ever a command needs to be run from a custom path, one way to
achieve this is:

.. code:: python

        shell = LocalShell()
        os.chdir(first_custom_path)
        shell('first_command')
        os.chdir(second_custom_path)
        shell('second_command')

This works ... but it is ugly! Two levels of abstraction are mixed.

This is better:

.. code:: python

        shell = LocalShell()
        shell('first_command', cwd=first_custom_path)
        shell('second_command', cwd=second_custom_path)

The shell can raise an exception if the exit code is non-zero:

.. code:: python

    assert shell("exit 13").exit_code() == 13 # will not raise any exception
    try:
        shell("exit 13", check_xc=True) # will raise an exception
        assert False, "will not be reached"
    except ShellError as e:
        assert True, "will be reached"

The shell can also raise an exception if something is printed on the
standard error:

.. code:: python

    shell("echo DANGER >&2").stderr() == ["DANGER"] # will not raise any exception
    try:
        shell("echo DANGER >&2", check_err=True) # will raise an exception
        assert False, "will not be reached"
    except ShellError as e:
        assert True, "will be reached"

You can pull file from the remote host (for ``LocalShell`` it's just
doing a copy):

.. code:: python

    shell("echo -n test > remote_file.txt")
    shell.pull("local_file.txt", "remote_file.txt")
    assert open("local_file.txt", "r").read() == "test"

or push file to the remote host (again, for ``LocalShell`` it's just
doing a copy):

.. code:: python

    open("local_file.txt", "w").write("test")
    shell.push("local_file.txt", "remote_file.txt")
    assert str(shell("cat remote_file.txt")) == "test"

.. |Build Status| image:: https://travis-ci.org/meuter/citizenshell.svg?branch=master
   :target: https://travis-ci.org/meuter/citizenshell


