{"id":13419371,"url":"https://github.com/tusharsadhwani/zxpy","last_synced_at":"2025-05-16T01:07:51.398Z","repository":{"id":43183159,"uuid":"365218255","full_name":"tusharsadhwani/zxpy","owner":"tusharsadhwani","description":"Shell scripts made simple 🐚","archived":false,"fork":false,"pushed_at":"2024-09-06T22:07:47.000Z","size":132,"stargazers_count":662,"open_issues_count":6,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-08T08:05:58.615Z","etag":null,"topics":["python","shell"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/zxpy","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tusharsadhwani.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-07T11:56:11.000Z","updated_at":"2025-05-07T11:39:52.000Z","dependencies_parsed_at":"2024-05-29T12:13:25.477Z","dependency_job_id":"9a427dbb-8a34-4c3c-a532-e56b78c3c4ca","html_url":"https://github.com/tusharsadhwani/zxpy","commit_stats":{"total_commits":148,"total_committers":3,"mean_commits":"49.333333333333336","dds":"0.013513513513513487","last_synced_commit":"6f2abc1cc1f0af40b1119ad974678bba75682d7d"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tusharsadhwani%2Fzxpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tusharsadhwani%2Fzxpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tusharsadhwani%2Fzxpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tusharsadhwani%2Fzxpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tusharsadhwani","download_url":"https://codeload.github.com/tusharsadhwani/zxpy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254448578,"owners_count":22072764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python","shell"],"created_at":"2024-07-30T22:01:15.015Z","updated_at":"2025-05-16T01:07:46.371Z","avatar_url":"https://github.com/tusharsadhwani.png","language":"Python","funding_links":[],"categories":["Python","Shell","Programming"],"sub_categories":["Libraries"],"readme":"# zxpy\n\n[![Downloads](https://static.pepy.tech/badge/zxpy)](https://pepy.tech/project/zxpy)\n[![Code style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![CI Status](https://github.com/tusharsadhwani/zxpy/actions/workflows/tox.yml/badge.svg)](https://github.com/tusharsadhwani/zxpy/actions/workflows/tox.yml)\n\nShell scripts made simple 🐚\n\nzxpy lets you seamlessly write shell commands inside Python code, to create readable and maintainable shell scripts.\n\nInspired by Google's [zx](https://github.com/google/zx), but made much simpler and more accessible using Python.\n\n## Rationale\n\nBash is cool, and it's extremely powerful when paired with linux coreutils and pipes. But apart from that, it's a whole another language to learn, and has a (comparatively) unintuitive syntax for things like conditionals and loops.\n\n`zxpy` aims to supercharge bash by allowing you to write scripts in Python, but with native support for bash commands and pipes.\n\nLet's use it to find all `TODO`s in one of my other projects, and format them into a table:\n\n```python\n#! /usr/bin/env zxpy\ntodo_comments = ~\"git grep -n TODO\"\nfor todo in todo_comments.splitlines():\n    filename, lineno, code = todo.split(':', 2)\n    *_, comment = code.partition('TODO')\n    print(f\"{filename:40} on line {lineno:4}: {comment.lstrip(': ')}\")\n```\n\nRunning this, we get:\n\n```console\n$ ./todo_check.py\nREADME.md                                on line 154 : move this content somewhere more sensible.\ninstachat/lib/models/message.dart        on line 7   : rename to uuid\ninstachat/lib/models/update.dart         on line 13  : make int\ninstachat/lib/services/chat_service.dart on line 211 : error handling\nserver/api/api.go                        on line 94  : move these to /chat/@:address\nserver/api/user.go                       on line 80  : check for errors instead of relying on zero value\n```\n\nWriting something like this purely in bash or in Python would be much harder than this. Being able to use linux utilities seamlessly with a readable, general purpose language is what makes this a really powerful tool.\n\n### A larger, practical example\n\nYou can find a comparison between a practical-ish script written in bash and\nzxpy in [EXAMPLE.md](./EXAMPLE.md)\n\n## Installation \u003ca href=\"https://pypi.org/project/zxpy\"\u003e\u003cimg src=\"https://img.shields.io/badge/pypi-zxpy-blue?style=flat\"\u003e\u003c/a\u003e\n\n```console\npip install zxpy\n```\n\n### pipx\n\nIf you have `pipx` installed, you can try out zxpy without installing it, by running:\n\n```console\npipx run zxpy\n```\n\n## Basic Examples\n\nMake a file `script.py` (The name and extension can be anything):\n\n```python\n#! /usr/bin/env zxpy\n~'echo Hello world!'\n\nfile_count = ~'ls -1 | wc -l'\nprint(\"file count is:\", file_count)\n```\n\nAnd then run it:\n\n```console\n$ chmod +x ./script.py\n\n$ ./script.py\nHello world!\nfile count is: 3\n```\n\n\u003e Run `\u003e\u003e\u003e help('zx')` in Python REPL to find out more ways to use zxpy.\n\nA slightly more involved example: [run_all_tests.py](./examples/run_all_tests.py)\n\n```python\n#! /usr/bin/env zxpy\ntest_files = (~\"find -name '*_test\\.py'\").splitlines()\n\nfor filename in test_files:\n    try:\n        print(f'Running {filename:.\u003c50}', end='')\n        output = ~f'python {filename}'  # variables in your shell commands :D\n        assert output == ''\n        print('Test passed!')\n    except:\n        print(f'Test failed.')\n```\n\nOutput:\n\n```console\n$ ./run_all_tests.py\nRunning ./tests/python_version_test.py....................Test failed.\nRunning ./tests/platform_test.py..........................Test passed!\nRunning ./tests/imports_test.py...........................Test passed!\n```\n\nMore examples are in [EXAMPLE.md](./EXAMPLE.md), and in the [examples folder](./examples).\n\n## `stderr` and return codes\n\nTo get `stderr` and return code information out of the shell command, there is an\nalternative way of invoking the shell.\n\nTo use it, just use **3 variables** on the\nleft side of your `~'...'` shell string:\n\n```python\nstdout, stderr, return_code = ~'echo hi'\nprint(stdout)       # hi\nprint(return_code)  # 0\n```\n\nMore examples are in the [examples folder](./examples).\n\n## CLI Arguments\n\nWhen writing a shell script, you often want to pass CLI arguments to it.\n\nLike so:\n\n```console\n$ cat ./foo.sh\necho arg is: $1\n\n$ ./foo.sh 123\narg is: 123\n```\n\nTo do the same in `zxpy`, pass the script arguments after a `--` in the `zxpy` CLI command.\n\n```python\n#!/usr/bin/env zxpy\n\nimport sys\nprint(\"Argv is:\", sys.argv)\n\n~\"echo output: $1 $2 $3\"\n```\n\n```console\n$ ./test.py\nArgv is: ['/bin/sh']\noutput:\n\n$ ./test.py -- abc def\nArgv is: ['/bin/sh', 'abc', 'def']\noutput: abc def\n```\n\nBoth `$1` and `sys.argv[1]` will do the same thing.\n\n## Quoting\n\nTake this shell command:\n\n```console\n$ uname -a\nLinux pop-os 5.11.0 [...] x86_64 GNU/Linux\n```\n\nNow take this piece of code:\n\n```pycon\n\u003e\u003e\u003e cmd = 'uname -a'\n\u003e\u003e\u003e ~f'{cmd}'\n/bin/sh: 1: uname -a: not found\n```\n\nWhy does this not work?\n\nThis is because `uname -a` was **quoted** into `'uname -a'`. All values passed\ninside f-strings are automatically quoted to avoid [shell injection][1].\n\nTo prevent quoting, the `:raw` format_spec can be used:\n\n```pycon\n\u003e\u003e\u003e cmd = 'uname -a'\n\u003e\u003e\u003e ~f'{cmd:raw}'\nLinux pop-os 5.11.0 [...] x86_64 GNU/Linux\n```\n\nThis _disables_ quoting, and the command is run as-is as provided in the string.\n\n\u003e Note that this shouldn't be used with external data, or this _will_ expose you\n\u003e to [shell injection][1].\n\n## Interactive mode\n\n```pycon\n$ zxpy\nzxpy shell\nPython 3.8.5 (default, Jan 27 2021, 15:41:15)\n[GCC 9.3.0]\n\n\u003e\u003e\u003e ~\"ls | grep '\\.py'\"\n__main__.py\nsetup.py\nzx.py\n\u003e\u003e\u003e\n```\n\n\u003e Also works with `path/to/python -m zx`\n\nIt can also be used to start a zxpy session in an already running REPL.\nSimply do:\n\n```pycon\n\u003e\u003e\u003e import zx; zx.install()\n```\n\nand zxpy should be enabled in the existing session.\n\n## Development/Testing\n\nTo install from source, clone the repo, and do the following:\n\n```console\n$ source ./venv/bin/activate  # Always use a virtualenv!\n$ pip install -r requirements-dev.txt\nProcessing ./zxpy\n[...]\nSuccessfully installed zxpy-1.X.X\n$ pytest  # runs tests\n```\n\n[1]: https://owasp.org/www-community/attacks/Command_Injection\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftusharsadhwani%2Fzxpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftusharsadhwani%2Fzxpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftusharsadhwani%2Fzxpy/lists"}