Skip to main content

Description

The shell_utils library provides some handy utilities for when you need to automate certain processes using shell commands.

Where you might otherwise write a bash script or muck around with the subprocess, os, and sys modules in a Python script shell_utils provides some patterns and shortcuts for your automation scripts.

Let's say we have a new project we need to automate some build process(es) for. We might be tempted to write a Makefile or bash script(s) to help with that task. If that works for you, great. However, if you're like me, you prefer to python-all-the-things.

We can use shell-utils to create an automation script that will behave much the same way a Makefile would, but with all the Python goodness we want.

Some familiarity with the click library will be helpful.

pip3 install shell_utils
shell_utils generate_runner

This will produce an executable python script with the following code

#!/usr/bin/env python3
import os
from pathlib import Path

from shell_utils import shell, cd, env, path, quiet

import click


@click.group()
def main():
    """
    Development tasks; programmatically generated
    """

    # ensure we're running commands from project root

    root = Path(__file__).parent.absolute()
    cwd = Path().absolute()

    if root != cwd:
        click.secho(f'Navigating from {cwd} to {root}',
                    fg='yellow')
        os.chdir(root)


if __name__ == '__main__':
    main()

Now let's say that we're using sphinx to generate the documentation we have in our project's docs directory.

If we wanted to create a command that would re-generate our documentation and open a browser window when it's finished,

we could add the following code to our generated run.py script

@main.command()
@click.option('--no-browser', is_flag=True, help="Don't open browser after building docs.")
def docs(no_browser):
    """
    Generate Sphinx HTML documentation, including API docs.
    """
    shell(
        """
        rm -f docs/shell_utils.rst
        rm -f docs/modules.rst
        rm -rf docs/shell_utils*
        sphinx-apidoc -o docs/ shell_utils
        """
    )

    with cd('docs'):
        shell('make clean')
        shell('make html')

    shell('cp -rf docs/_build/html/ public/')

    if no_browser:
        return

    shell('open public/index.html')

Then, we can execute the following command to do what we intended:

./run.py docs

The strings sent to the shell function will be executed in a bash subprocess shell. Before they are executed, the shell function will print the command to stdout, similar to a Makefile.

Also, notice we change directories into docs using a context manager, that way the commands passed to the shell function will execute within that directory. Once we're out of the context manager's scope, further shell function commands are once-again run from the project root.

functions and context managers

shell

Executes the given command in a bash shell. It's just a thin wrapper around subprocess.run that adds a couple handy features, such as printing the command it's about to run before executing it.

from shell_utils import shell

p1 = shell('echo hello, world')

print(p1)

p2 = shell('echo goodbye, cruel world', capture=True)

print('captured the string:', p2.stdout)

outputs

user@hostname executing...

echo goodbye, cruel world


captured the string: goodbye, cruel world

cd

Temporarily changes the current working directory while within the context scope.

Within a python shell...

from shell_utils import shell, cd

with cd('~'):
    shell('echo $PWD')
    shell('mkdir -p foo')
    with cd('foo'):
        shell('echo $PWD')
    shell('echo $PWD')

outputs

user@hostname executing...

echo $PWD

/Users/user


user@hostname executing...

mkdir -p foo



user@hostname executing...

echo $PWD

/Users/user/foo


user@hostname executing...

echo $PWD

/Users/user

env

Temporarily changes environment variables

from shell_utils import env
import os

print(os.getenv('foo', 'nothing'))

with env(foo='bar'):
    print(os.getenv('foo'))

print(os.getenv('foo', 'nothing again'))

outputs

nothing
bar
nothing again

path

A special case of the env context manager that alters your $PATH. It expands ~ to your home directory and returns the elements of the $PATH variable as a list.

from shell_utils import path
import os

def print_path():
    print('$PATH ==', os.getenv('PATH'))

print_path()

with path('~', prepend=True) as plist:
    print_path()
    print(plist)

outputs

$PATH == /Users/user/.venvs/shell-utils-py3.7/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin
$PATH == /Users/user:/Users/user/.venvs/shell-utils-py3.7/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin
['/Users/user', '/Users/user/.venvs/shell-utils-py3.7/bin', '/usr/local/sbin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/Library/TeX/texbin']

Metadata

Release files for shell-utils 2.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shell-utils 2.3.1
File Size Uploaded Provenance
shell_utils-2.3.1.tar.gz 7.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for shell-utils 2.3.1
File Interpreter ABI Platform
shell_utils-2.3.1-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 22.3 kB

Release files / shell_utils-2.3.1.tar.gz

Download URL shell_utils-2.3.1.tar.gz
Size 7.5 kB
Tags Source
SHA-256 checksum
How to use checksums
08e45b3675a3de7b413008016689a5025adfedb6f46f729e55cb7ebb2afaf963
BLAKE2b-256 checksum
How to use checksums
a967d74b0d95e120126113ee62f3085d684fbd48e33091bcf6e74684e8809875
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/0.11.5 CPython/3.7.0 Darwin/17.7.0

Release files / shell_utils-2.3.1-py2.py3-none-any.whl

Download URL shell_utils-2.3.1-py2.py3-none-any.whl
Size 14.8 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
9e304e60d739936f13f888ee38acd5ebe8f022f845ae0da1f89eb3e3bbbcc8cf
BLAKE2b-256 checksum
How to use checksums
80a264efc899b64924a5fd3186d6f17df6650c1641d5966fe7908267a7030bef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/0.11.5 CPython/3.7.0 Darwin/17.7.0

Security for shell-utils 2.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page