Skip to main content

Shell automation tools, like Make on steroids.

Project description

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 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 to stdout 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.decode())

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

shell('pwd')
user@hostname executing...
pwd
.../shell_utils
CompletedProcess(args='pwd', returncode=0)

with cd('tests'):
    shell('pwd')
    
user@hostname executing...
pwd
.../shell_utils/tests

shell('pwd')
user@hostname executing...
pwd
.../shell_utils
CompletedProcess(args='pwd', returncode=0)

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']

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shell_utils-0.6.2.tar.gz (6.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shell_utils-0.6.2-py2.py3-none-any.whl (12.4 kB view details)

Uploaded Python 2Python 3

File details

Details for the file shell_utils-0.6.2.tar.gz.

File metadata

  • Download URL: shell_utils-0.6.2.tar.gz
  • Upload date:
  • Size: 6.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/0.11.4 CPython/2.7.15 Darwin/17.7.0

File hashes

Hashes for shell_utils-0.6.2.tar.gz
Algorithm Hash digest
SHA256 758972fe442cb385a7047dfc2b8dae212ef7894b1737aa46b545aa1250a8962c
MD5 2832d21fde569b511bf2b2ff6d712891
BLAKE2b-256 6113f9692d1495503095a6e77854884f09fdf23ddc2aa5569c344eea3f640626

See more details on using hashes here.

File details

Details for the file shell_utils-0.6.2-py2.py3-none-any.whl.

File metadata

  • Download URL: shell_utils-0.6.2-py2.py3-none-any.whl
  • Upload date:
  • Size: 12.4 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/0.11.4 CPython/2.7.15 Darwin/17.7.0

File hashes

Hashes for shell_utils-0.6.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 7a8976878da921d8ad1241181931818cf5a06b6a49bb719c216e6826326f05af
MD5 0d17229ce436e31b300f60774bd99bc4
BLAKE2b-256 dc046935fcf4fb614d791b628e1a8d18f8a60fcd0817b0669e8a54817be25a31

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page