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