Package: shell-utils
DescriptionTheshell_utilslibrary 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 thesubprocess,os, andsysmodules in a Python scriptshell_utilsprovides
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 theclicklibrary will be helpful.pip3installshell_utils
shell_utilsgenerate_runnerThis will produce an executable python script with the following code#!/usr/bin/env python3importosfrompathlibimportPathfromshell_utilsimportshell,cd,env,path,quietimportclick@click.group()defmain():"""Development tasks; programmatically generated"""# ensure we're running commands from project rootroot=Path(__file__).parent.absolute()cwd=Path().absolute()ifroot!=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'sdocsdirectory.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 generatedrun.pyscript@main.command()@click.option('--no-browser',is_flag=True,help="Don't open browser after building docs.")defdocs(no_browser):"""Generate Sphinx HTML documentation, including API docs."""shell("""rm -f docs/shell_utils.rstrm -f docs/modules.rstrm -rf docs/shell_utils*sphinx-apidoc -o docs/ shell_utils""")withcd('docs'):shell('make clean')shell('make html')shell('cp -rf docs/_build/html/ public/')ifno_browser:returnshell('open public/index.html')Then, we can execute the following command to do what we intended:./run.py docsThe strings sent to theshellfunction will be executed in abashsubprocess shell. Before they are executed,
theshellfunction will print the command tostdout, similar to aMakefile.Also, notice we change directories intodocsusing a context manager, that way the commands passed to theshellfunction
will execute within that directory. Once we're out of the context manager's scope, furthershellfunction commands are once-again run
from the project root.functions and context managersshellExecutes the given command in a bash shell. It's just a thin wrapper aroundsubprocess.runthat adds a couple handy features,
such as printing the command it's about to run before executing it.fromshell_utilsimportshellp1=shell('echo hello, world')print(p1)p2=shell('echo goodbye, cruel world',capture=True)print('captured the string:',p2.stdout)outputsuser@hostnameexecuting...echogoodbye,cruelworld


capturedthestring:goodbye,cruelworldcdTemporarily changes the current working directory while within the context scope.Within a python shell...fromshell_utilsimportshell,cdwithcd('~'):shell('echo $PWD')shell('mkdir -p foo')withcd('foo'):shell('echo $PWD')shell('echo $PWD')outputsuser@hostnameexecuting...echo$PWD/Users/user


user@hostnameexecuting...

mkdir-pfoo



user@hostnameexecuting...echo$PWD/Users/user/foo


user@hostnameexecuting...echo$PWD/Users/userenvTemporarily changes environment variablesfromshell_utilsimportenvimportosprint(os.getenv('foo','nothing'))withenv(foo='bar'):print(os.getenv('foo'))print(os.getenv('foo','nothing again'))outputsnothing
bar
nothingagainpathA special case of theenvcontext manager that alters your $PATH. It expands~to your home directory and returns
the elements of the $PATH variable as a list.fromshell_utilsimportpathimportosdefprint_path():print('$PATH ==',os.getenv('PATH'))print_path()withpath('~',prepend=True)asplist: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']