citizenshell


Namecitizenshell JSON
Version 2.3.2 PyPI version JSON
download
home_pagehttps://github.com/meuter/citizenshell
SummaryInteract with shell locally or over different connection types (telnet, ssh, serial, adb)
upload_time2024-01-21 10:31:28
maintainer
docs_urlNone
authorCedric Meuter
requires_python
licenseMIT
keywords shell telnet adb ssh serial
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # citizenshell [![Build Status](https://travis-ci.org/meuter/citizenshell.svg?branch=master)](https://travis-ci.org/meuter/citizenshell)

__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:

    ```python
    from citizenshell import sh
    ```

2. you can instanciate your own `LocalShell`:

    ```python
    from citizenshell import LocalShell

    shell = LocalShell()
    ```

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

    ```python
    from citizenshell import TelnetShell

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

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

    ```python
    from citizenshell import SecureShell

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

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

    - if ADB devices is reachable over TCP/IP:

      ```python
      from citizenshell import AdbShell

      shell = AdbShell(hostname="acme.org", port=5555)
      ```

    - if ADB device is connected via USB:

      ```python
      from citizenshell import AdbShell

      shell = AdbShell(device="1c123a09dab45cbf")
      ```

    - if there is only one ADB device connected via USB:

      ```python
      from citizenshell import AdbShell

      shell = AdbShell()
      ```

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

    ```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:

    ```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")
    adbtcpshell = Shell("adb+tcp://myandroiddevice:5555")
    adbtcpshell = Shell("adb+usb://1c123a09dab45cbf")
    serialshell = Shell("serial://jogn:secretpassword@/dev/ttyUSB3?baudrate=115200")
    ```

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

    ```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:

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

You can also iterate over the standard output:

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

```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:

```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:

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

```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:

```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:

```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:

```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:

```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):

```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):

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

## Logs

Every shell object has a set of loggers: stdin, stderr and stdout, as well as for out of band logging message. 
By default they are all set to `logging.CRITICAL` which does not log anything. However, this log level
can be configured either using the `log_level=` keyword argument in the shell constructor:

```python
from citizenshell import LocalShell
from logging import INFO

shell = LocalShell(log_level=INFO)
```

or by calling the `set_log_level()` method:

```python
from citizenshell import sh
from logging import INFO

sh.set_log_level(INFO)
```

When configured with `logging.INFO`:
- all commands are logged on stdout prefixed by a `$` and colored in cyan with `termcolor`
- all characters produced on stdout are logged to stdout
- all characters produced on stderr are logged to stderr and colored in red with `termcolor`
- all out of band messages are logged to stdout prefixed with `>` and colored in yello with `termcolor`

For example:

```python
from citizenshell import LocalShell
from logging import INFO

shell = LocalShell(log_level=INFO)
shell(">&2 echo error && echo output && exit 13")
shell("echo Hello > /tmp/from.txt")
shell.push("/tmp/from.txt", "/tmp/to.txt")
```

will produce the following logs (colors are omitted):

```
$ >&2 echo error && echo output && exit 13
output
error
$ echo Hello > /tmp/from.txt
> '/tmp/from.txt' -> '/tmp/to.txt'
$ command -v chmod
/bin/chmod
$ chmod 664 '/tmp/to.txt'
```

For more even more logs messages, `logging.DEBUG` can be used. 




            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/meuter/citizenshell",
    "name": "citizenshell",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "shell,telnet,adb,ssh,serial",
    "author": "Cedric Meuter",
    "author_email": "cedric.meuter@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/c3/2b/2145c24989bf6e8aa63d2007009ed327781701507f6d589ed6fb0bf064a1/citizenshell-2.3.2.tar.gz",
    "platform": null,
    "description": "# citizenshell [![Build Status](https://travis-ci.org/meuter/citizenshell.svg?branch=master)](https://travis-ci.org/meuter/citizenshell)\n\n__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\n[MIT](https://opensource.org/licenses/MIT) license.\n\n## Installation\n\n__citizenshell__ can simply installed using `pip install citizenshell`\n\n## Obtaining a shell\n\nFirst you need a shell. For that you have several options:\n\n1. use the built-in `LocalShell` for quick access:\n\n    ```python\n    from citizenshell import sh\n    ```\n\n2. you can instanciate your own `LocalShell`:\n\n    ```python\n    from citizenshell import LocalShell\n\n    shell = LocalShell()\n    ```\n\n3. you can instanciate the `TelnetShell` for shell over telnet:\n\n    ```python\n    from citizenshell import TelnetShell\n\n    shell = TelnetShell(hostname=\"acme.org\", username=\"john\",\n                        password=\"secretpassword\")\n    ```\n\n4. you can instanciate the `SecureShell` for shell over SSH:\n\n    ```python\n    from citizenshell import SecureShell\n\n    shell = SecureShell(hostname=\"acme.org\", username=\"john\",\n                        password=\"secretpassword\")\n    ```\n\n5. you can instanciate the `AdbShell` for shell over ADB:\n\n    - if ADB devices is reachable over TCP/IP:\n\n      ```python\n      from citizenshell import AdbShell\n\n      shell = AdbShell(hostname=\"acme.org\", port=5555)\n      ```\n\n    - if ADB device is connected via USB:\n\n      ```python\n      from citizenshell import AdbShell\n\n      shell = AdbShell(device=\"1c123a09dab45cbf\")\n      ```\n\n    - if there is only one ADB device connected via USB:\n\n      ```python\n      from citizenshell import AdbShell\n\n      shell = AdbShell()\n      ```\n\n6. you can instanciate the `SerialShell` for shell over serial line:\n\n    ```python\n    from serial import EIGHTBITS, PARITY_NONE\n    from citizenshell import SerialShell\n\n    shell = SerialShell(port=\"/dev/ttyUSB3\", username=\"john\",\n                        password=\"secretpassword\",\n                        baudrate=115200, parity=PARITY_NONE, bytesize=EIGHTBITS)\n    ```\n\n7. you can also obtain shell objects by URI using the `Shell` function:\n\n    ```python\n    from citizenshell import Shell\n\n    localshell  = Shell()\n    telnetshell = Shell(\"telnet://john:secretpassword@acme.org:1234\")\n    secureshell = Shell(\"ssh://john:secretpassword@acme.org:1234\")\n    adbshell    = Shell(\"adb://myandroiddevice:5555\")\n    adbtcpshell = Shell(\"adb+tcp://myandroiddevice:5555\")\n    adbtcpshell = Shell(\"adb+usb://1c123a09dab45cbf\")\n    serialshell = Shell(\"serial://jogn:secretpassword@/dev/ttyUSB3?baudrate=115200\")\n    ```\n\n    you can also mix and match betweens providing arguments in the URI or via kwargs:\n\n    ```python\n    telnetshell = Shell(\"telnet://john@acme.org\", password=\"secretpassword\", port=1234)\n    serialshell = Shell(\"serial://john:secretpassword@/dev/ttyUSB3\", baudrate=115200)\n    ```\n\n## Using a shell\n\nOnce you have shell, any shell, you can call it directly and get the standart output:\n\n```python\nassert shell(\"echo Hello World\") == \"Hello World\"\n```\n\nYou can also iterate over the standard output:\n\n```python\nresult = [int(x) for x in shell(\"\"\"\n    for i in 1 2 3 4; do\n        echo $i;\n    done\n\"\"\")]\nassert result == [1, 2, 3, 4]\n```\n\nYou don't have to wait for the command to finish to receive the output.\n\nThis loop\n\n```python\nfor line in shell(\"for i in 1 2 3 4; do echo -n 'It is '; date +%H:%M:%S; sleep 1; done\", wait=False):\n    print \">>>\", line + \"!\"\n```\n\nwould produce something like:\n\n```text\n>>> It is 14:24:52!\n>>> It is 14:24:53!\n>>> It is 14:24:54!\n>>> It is 14:24:55!\n```\n\nYou can extract stdout, stderr and exit code seperately:\n\n```python\nresult = shell(\">&2 echo error && echo output && exit 13\")\nassert result.stdout() == [\"output\"]\nassert result.stderr() == [\"error\"]\nassert result.exit_code() == 13\n```\n\nYou can inject environment variable to the shell\n\n```python\nassert shell(\"echo $VAR\", VAR=\"bar\") == \"bar\"\n```\n\nBy default, shell inherits \"$CWD\" from the environment (aka $PWD).\n\nStill, if ever a command needs to be run from a custom path, one\nway to achieve this is:\n\n```python\n    shell = LocalShell()\n    os.chdir(first_custom_path)\n    shell('first_command')\n    os.chdir(second_custom_path)\n    shell('second_command')\n```\n\nThis works ... but it is ugly! Two levels of abstraction are mixed.\n\nThis is better:\n\n```python\n    shell = LocalShell()\n    shell('first_command', cwd=first_custom_path)\n    shell('second_command', cwd=second_custom_path)\n```\n\nThe shell can raise an exception if the exit code is non-zero:\n\n```python\nassert shell(\"exit 13\").exit_code() == 13 # will not raise any exception\ntry:\n    shell(\"exit 13\", check_xc=True) # will raise an exception\n    assert False, \"will not be reached\"\nexcept ShellError as e:\n    assert True, \"will be reached\"\n```\n\nThe shell can also raise an exception if something is printed on the standard error:\n\n```python\nshell(\"echo DANGER >&2\").stderr() == [\"DANGER\"] # will not raise any exception\ntry:\n    shell(\"echo DANGER >&2\", check_err=True) # will raise an exception\n    assert False, \"will not be reached\"\nexcept ShellError as e:\n    assert True, \"will be reached\"\n```\n\nYou can pull file from the remote host (for `LocalShell` it's just doing a copy):\n\n```python\nshell(\"echo -n test > remote_file.txt\")\nshell.pull(\"local_file.txt\", \"remote_file.txt\")\nassert open(\"local_file.txt\", \"r\").read() == \"test\"\n```\n\nor push file to the remote host (again, for `LocalShell` it's just doing a copy):\n\n```python\nopen(\"local_file.txt\", \"w\").write(\"test\")\nshell.push(\"local_file.txt\", \"remote_file.txt\")\nassert str(shell(\"cat remote_file.txt\")) == \"test\"\n```\n\n## Logs\n\nEvery shell object has a set of loggers: stdin, stderr and stdout, as well as for out of band logging message. \nBy default they are all set to `logging.CRITICAL` which does not log anything. However, this log level\ncan be configured either using the `log_level=` keyword argument in the shell constructor:\n\n```python\nfrom citizenshell import LocalShell\nfrom logging import INFO\n\nshell = LocalShell(log_level=INFO)\n```\n\nor by calling the `set_log_level()` method:\n\n```python\nfrom citizenshell import sh\nfrom logging import INFO\n\nsh.set_log_level(INFO)\n```\n\nWhen configured with `logging.INFO`:\n- all commands are logged on stdout prefixed by a `$` and colored in cyan with `termcolor`\n- all characters produced on stdout are logged to stdout\n- all characters produced on stderr are logged to stderr and colored in red with `termcolor`\n- all out of band messages are logged to stdout prefixed with `>` and colored in yello with `termcolor`\n\nFor example:\n\n```python\nfrom citizenshell import LocalShell\nfrom logging import INFO\n\nshell = LocalShell(log_level=INFO)\nshell(\">&2 echo error && echo output && exit 13\")\nshell(\"echo Hello > /tmp/from.txt\")\nshell.push(\"/tmp/from.txt\", \"/tmp/to.txt\")\n```\n\nwill produce the following logs (colors are omitted):\n\n```\n$ >&2 echo error && echo output && exit 13\noutput\nerror\n$ echo Hello > /tmp/from.txt\n> '/tmp/from.txt' -> '/tmp/to.txt'\n$ command -v chmod\n/bin/chmod\n$ chmod 664 '/tmp/to.txt'\n```\n\nFor more even more logs messages, `logging.DEBUG` can be used. \n\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Interact with shell locally or over different connection types (telnet, ssh, serial, adb)",
    "version": "2.3.2",
    "project_urls": {
        "Download": "https://github.com/meuter/citizenshell/archive/2.3.2.tar.gz",
        "Homepage": "https://github.com/meuter/citizenshell"
    },
    "split_keywords": [
        "shell",
        "telnet",
        "adb",
        "ssh",
        "serial"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b3b2344cbc98efff9e015d9e67d24f65aa736a0531c68af62e8fae12f5b7a804",
                "md5": "b9281245e526a20a188de701880ed98d",
                "sha256": "b8e60a19013d6f46f01f085ca7bf85ad1a423ee58bfcdc9b7ab24e66ab9cb2f1"
            },
            "downloads": -1,
            "filename": "citizenshell-2.3.2-py2-none-any.whl",
            "has_sig": false,
            "md5_digest": "b9281245e526a20a188de701880ed98d",
            "packagetype": "bdist_wheel",
            "python_version": "py2",
            "requires_python": null,
            "size": 17812,
            "upload_time": "2024-01-21T10:31:26",
            "upload_time_iso_8601": "2024-01-21T10:31:26.203655Z",
            "url": "https://files.pythonhosted.org/packages/b3/b2/344cbc98efff9e015d9e67d24f65aa736a0531c68af62e8fae12f5b7a804/citizenshell-2.3.2-py2-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c32b2145c24989bf6e8aa63d2007009ed327781701507f6d589ed6fb0bf064a1",
                "md5": "082c97120c13c7be6378d5248cb81935",
                "sha256": "d67b2f631943c7d8d28a16395939f46ec162adffd221ff40e0d661597508a323"
            },
            "downloads": -1,
            "filename": "citizenshell-2.3.2.tar.gz",
            "has_sig": false,
            "md5_digest": "082c97120c13c7be6378d5248cb81935",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 17902,
            "upload_time": "2024-01-21T10:31:28",
            "upload_time_iso_8601": "2024-01-21T10:31:28.025931Z",
            "url": "https://files.pythonhosted.org/packages/c3/2b/2145c24989bf6e8aa63d2007009ed327781701507f6d589ed6fb0bf064a1/citizenshell-2.3.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-21 10:31:28",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "meuter",
    "github_project": "citizenshell",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "citizenshell"
}
        
Elapsed time: 0.21188s