Skip to main content

Python subprocess wrapper

Project description

``sh`` is a unique subprocess wrapper that maps your system programs to
Python functions dynamically. ``sh`` helps you write shell scripts in
Python by giving you the good features of Bash (easy command calling, easy
piping) with all the power and flexibility of Python.

from sh import ifconfig
print ifconfig("eth0")

``sh`` is not a collection of system commands implemented in Python.

# Getting

$> pip install sh

# Usage

The easiest way to get up and running is to import sh
directly or import your program from sh:

import sh
print sh.ifconfig("eth0")

from sh import ifconfig
print ifconfig("eth0")

A less common usage pattern is through ``sh`` Command wrapper, which takes a
full path to a command and returns a callable object. This is useful for
programs that have weird characters in their names or programs that aren't in
your $PATH:

import sh
ffmpeg = sh.Command("/usr/bin/ffmpeg")

The last usage pattern is for trying ``sh`` through an interactive REPL. By
default, this acts like a star import (so all of your system programs will be
immediately available as functions):

$> python

# Examples

## Executing Commands

Commands work like you'd expect. **Just call your program's name like
a function:**

# print the contents of this directory
print ls("-l")

# get the longest line of this file
longest_line = wc(__file__, "-L")

# get interface information
print ifconfig("eth0")

Note that these aren't Python functions, these are running the binary
commands on your system dynamically by resolving your PATH, much like Bash does.
In this way, all the programs on your system are easily available
in Python.

You can also call attributes on commands. This translates to the command
name followed by the attribute name:

from sh import git

# resolves to "git branch -v"
print git.branch("-v")

It turns out this is extremely useful for commands whose first argument is often
another sub-command (like git, svn, time, sudo, etc).
See "Baking" for an advanced usage of this.

## Keyword Arguments

Keyword arguments also work like you'd expect: they get replaced with the
long-form and short-form commandline option:

# resolves to "curl -o page.html --silent"
curl("", o="page.html", silent=True)

# or if you prefer not to use keyword arguments, this does the same thing:
curl("", "-o", "page.html", "--silent")

# resolves to "adduser amoffat --system --shell=/bin/bash --no-create-home"
adduser("amoffat", system=True, shell="/bin/bash", no_create_home=True)

# or
adduser("amoffat", "--system", "--shell", "/bin/bash", "--no-create-home")

## Piping

Piping has become function composition:

# sort this directory by biggest file
print sort(du(glob("*"), "-sb"), "-rn")

# print the number of folders and files in /etc
print wc(ls("/etc", "-1"), "-l")

## Redirection

``sh`` can redirect the standard and error output streams of a process to a file.
This is done with the special _out and _err keyword arguments. You can pass a
filename or a file object as the argument value. When the name of an already
existing file is passed, the contents of the file will be overwritten.

ls("nonexistent", _err="error.txt")

``sh`` can also redirect the error output stream to the standard output stream,
using the special _err_to_out=True keyword argument.

## Sudo and With Contexts

Commands can be run within a "with" context. Popular commands using this
might be "sudo" or "fakeroot":

with sudo:
print ls("/root")

If you need
to run a command in a with context AND call it, for example, specifying
a -p prompt with sudo, you need to use the "_with" keyword argument.
This let's the command know that it's being run from a with context so
it can behave correctly.

with sudo(p=">", _with=True):
print ls("/root")

## Background Processes

Commands can be run in the background with the special _bg=True keyword

# blocks
print "...3 seconds later"

# doesn't block
p = sleep(3, _bg=True)
print "prints immediately!"
print "...and 3 seconds later"

You can also pipe together background processes!

p = wc(curl("", silent=True, _bg=True), "--bytes")
print "prints immediately!"
print "byte count of github: %d" % int(p) # lazily completes

This lets you start long-running commands at the beginning of your script
(like a file download) and continue performing other commands in the

## Foreground Processes

Foreground processes are processes that you want to interact directly with
the default stdout and stdin of your terminal. In other words, these are
processes that you do not want to return their output as a return value
of their call. An example would be opening a text editor:


This will block because sh will be trying to aggregate the output
of the command to python, without displaying anything to the screen. The
solution is the "_fg" special keyword arg:

vim(file_to_edit, _fg=True)

This will open vim as expected and let you use it as expected, with all
the input coming from the keyboard and the output going to the screen.
The return value of a foreground process is an empty string.

## Finding Commands

"Which" finds the full path of a program, or returns None if it doesn't exist.
This command is one of the few commands implemented as a Python function,
and therefore doesn't rely on the "which" program actually existing.

print which("python") # "/usr/bin/python"
print which("ls") # "/bin/ls"
print which("some_command") # None

if not which("supervisorctl"): apt_get("install", "supervisor", "-y")

## Baking

``sh`` is capable of "baking" arguments into commands. This is similar
to the stdlib functools.partial wrapper. An example can speak volumes:

from sh import ls

ls = ls.bake("-la")
print ls # "/usr/bin/ls -la"

# resolves to "ls / -la"
print ls("/")

The idea is that calling "bake" on a command creates a callable object
that automatically passes along all of the arguments passed into "bake".
This gets **really interesting** when you combine this with the attribute
access on a command:

from sh import ssh

# calling whoami on the server. this is tedious to do if you're running
# any more than a few commands.
iam1 = ssh("", "-p 1393", "whoami")

# wouldn't it be nice to bake the common parameters into the ssh command?
myserver = ssh.bake("", p=1393)

print myserver # "/usr/bin/ssh -p 1393"

# resolves to "/usr/bin/ssh -p 1393 whoami"
iam2 = myserver.whoami()

assert(iam1 == iam2) # True!

Now that the "myserver" callable represents a baked ssh command, you
can call anything on the server easily:

# resolves to "/usr/bin/ssh -p 1393 tail /var/log/dumb_daemon.log -n 100"
print myserver.tail("/var/log/dumb_daemon.log", n=100)

## Environment Variables

Environment variables are available much like they are in Bash:

print HOME
print SHELL
print PS1

You can set enviroment variables the usual way, through the os.environ

import os
os.environ["TEST"] = "123"

Now any new subprocess commands called from the script will be able to
access that environment variable.

## Exceptions

Exceptions are dynamically generated based on the return code of the command.
This lets you catch a specific return code, or catch all error return codes
through the base class ErrorReturnCode:

try: print ls("/some/non-existant/folder")
except ErrorReturnCode_2:
print "folder doesn't exist!"
except ErrorReturnCode:
print "unknown error"

## Globbing

Glob-expansion is not done on your arguments. For example, this will not work:

from sh import du
print du("*")

You'll get an error to the effect of "cannot access '\*': No such file or directory".
This is because the "\*" needs to be glob expanded:

from sh import du, glob
print du(glob("*"))

## Commandline Arguments

You can access commandline arguments similar to Bash's $1, $2, etc by using
ARG1, ARG2, etc:

print ARG1, ARG2

# if an argument isn't defined, it's set to None
if ARG10 is None: do_something()

You can access the entire argparse/optparse-friendly list of commandline
arguments through "ARGV". This is recommended for flexibility:

import argparse
parser = argparse.ArgumentParser(prog="PROG")
parser.add_argument("-x", default=3, type=int)
ns = parser.parse_args(ARGV)
print ns.x

## Weirdly-named Commands

``sh`` automatically handles underscore-dash conversions. For example, if you want
to call apt-get:

apt_get("install", "mplayer", y=True)

``sh`` looks for "apt_get", but if it doesn't find it, replaces all underscores
with dashes and searches again. If the command still isn't found, a
CommandNotFound exception is raised.

Commands with other, less-commonly symbols in their names must be accessed
directly through the "Command" class wrapper. The Command class takes the full
path to the program as a string:

p27 = Command(which("python2.7"))
print p27("-h")

The Command wrapper is also useful for commands that are not in your standard PATH:

script = Command("/tmp/")
print script()

## Non-standard Exit Codes

Normally, if a command returns an exit code that is not 0, ``sh`` raises an exception
based on that exit code. However, if you have determined that an error code
is normal and want to retrieve the output of the command without ``sh`` raising an
exception, you can use the "_ok_code" special argument to suppress the exception:

output ="dir_that_exists", "dir_that_doesnt", _ok_code=2)

In the above example, even though you're trying to list a directory that doesn't
exist, you can still get the output from the directory that does exist by telling
the command that 2 is an "ok" exit code, so don't raise an exception.

_ok_code can also take a list or tuple of numbers for multiple ok exit codes.

Download files

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

Source Distribution

sh-0.107.tar.gz (13.1 kB view hashes)

Uploaded source

Supported by

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