Claude Code for plumbum: Python Shell Scripting — Claude Skills 360 Blog
Blog / AI / Claude Code for plumbum: Python Shell Scripting
AI

Claude Code for plumbum: Python Shell Scripting

Published: February 19, 2028
Read time: 5 min read
By: Claude Skills 360

plumbum wraps shell commands as Python objects. pip install plumbum. Local: from plumbum import local. ls = local["ls"]. ls() — run, return stdout. ls["-la"]() — with flag. ls["-la", "/tmp"]() — flag + arg. Bound: cmd = local["grep"]["-r", "pattern"]. cmd(".") — execute. git = local["git"]; git["commit", "-m", "msg"](). Pipeline: (ls | local["grep"]["py"])(). (cat << "input") | grep. Redirect: (ls > "/tmp/out.txt")(). (ls >> "/tmp/out.txt")() — append. (cmd > "/dev/null")() — discard. retcode: retcode, stdout, stderr = ls.run(retcode=None). cmd.run() → (rc, out, err). FG: from plumbum import FG; ls & FG — foreground, inherits terminal. BG: from plumbum import BG; future = ls & BG; future.wait(). TEE: from plumbum import TF; (ls & TF("/tmp/log.txt")). Error: from plumbum import ProcessExecutionError. cmd.run(retcode=[0,1]) — accept codes. env: with local.env(HOME="/tmp"):. cwd: with local.cwd("/tmp"):. SSH: from plumbum.machines import SshMachine; r = SshMachine("host"); r["ls"](). CLI: from plumbum import cli; class App(cli.Application):. @cli.switch("--verbose"). Claude Code generates plumbum command pipelines, shell wrappers, and SSH automation scripts.

CLAUDE.md for plumbum

## plumbum Stack
- Version: plumbum >= 1.8 | pip install plumbum
- Command: local["cmd"]["-flag", "arg"]() — run and return stdout
- Pipeline: (cmd1 | cmd2)() — pipe stdout | stdin
- Redirect: (cmd > "file")() | (cmd >> "file")() for append
- Retcode: cmd.run(retcode=None) → (rc, out, err) | retcode=[0,1] for multiple OK codes
- Env/cwd: with local.env(VAR="val"): | with local.cwd("/path"):
- Error: catch ProcessExecutionError — has .retcode .stdout .stderr

plumbum Shell Pipeline

# app/shell.py — plumbum command execution, pipelines, and SSH automation
from __future__ import annotations

import sys
from pathlib import Path
from typing import Any

from plumbum import BG, FG, ProcessExecutionError, local
from plumbum.machines import LocalMachine


# ─────────────────────────────────────────────────────────────────────────────
# 1. Command wrappers
# ─────────────────────────────────────────────────────────────────────────────

def run(cmd_name: str, *args: str, retcode: int | list[int] | None = 0) -> str:
    """
    Run a local command with arguments and return stdout.
    retcode=None accepts any exit code.
    retcode=[0,1] accepts exit codes 0 and 1.
    Raises ProcessExecutionError on unexpected exit codes.
    """
    cmd = local[cmd_name][args]
    rc, out, err = cmd.run(retcode=retcode)
    return out


def run_checked(cmd_name: str, *args: str) -> tuple[int, str, str]:
    """Run a command and return (returncode, stdout, stderr) regardless of exit code."""
    cmd = local[cmd_name][args]
    return cmd.run(retcode=None)


def run_lines(cmd_name: str, *args: str) -> list[str]:
    """Run command and return stdout as a list of non-empty lines."""
    out = run(cmd_name, *args)
    return [line for line in out.splitlines() if line.strip()]


# ─────────────────────────────────────────────────────────────────────────────
# 2. Pipeline operations
# ─────────────────────────────────────────────────────────────────────────────

def grep_files(pattern: str, path: str = ".", extension: str = "py") -> list[str]:
    """
    Pipe: find ... | grep pattern — returns matching lines.
    Plumbum pipeline: (cmd1 | cmd2)() runs both with connected pipes.
    """
    try:
        find = local["find"][path, "-name", f"*.{extension}", "-type", "f"]
        xargs = local["xargs"]["grep", "-l", pattern]
        result = (find | xargs)()
        return [l for l in result.splitlines() if l.strip()]
    except ProcessExecutionError as e:
        if e.retcode == 1:   # grep returns 1 when no matches
            return []
        raise


def count_lines(path: str) -> dict[str, int]:
    """
    Pipe: find | wc — count total lines across all Python files.
    Demonstrates chaining three commands in a pipeline.
    """
    try:
        find  = local["find"][path, "-name", "*.py", "-type", "f"]
        xargs = local["xargs"]["wc", "-l"]
        out   = (find | xargs)()
        # Parse "total" line from wc -l output
        for line in reversed(out.splitlines()):
            parts = line.split()
            if parts and parts[-1] == "total":
                return {"total_lines": int(parts[0])}
        return {"total_lines": 0}
    except ProcessExecutionError:
        return {"total_lines": 0}


def redirect_to_file(cmd_name: str, *args: str, output: str) -> None:
    """
    Redirect command output to a file.
    (cmd > "path")() overwrites; (cmd >> "path")() appends.
    """
    cmd = local[cmd_name][args]
    (cmd > output)()


# ─────────────────────────────────────────────────────────────────────────────
# 3. Git helpers
# ─────────────────────────────────────────────────────────────────────────────

_git = local["git"]


def git_status() -> str:
    """Return git status output."""
    rc, out, err = _git["status", "--short"].run(retcode=None)
    return out


def git_log(n: int = 10, format_str: str = "%h %s") -> list[str]:
    """Return last n commit messages."""
    out = _git["log", f"-{n}", f"--pretty=format:{format_str}"]()
    return [l for l in out.splitlines() if l]


def git_branch() -> str:
    """Return current branch name."""
    return _git["rev-parse", "--abbrev-ref", "HEAD"]().strip()


def git_changed_files() -> list[str]:
    """Return list of modified (uncommitted) files."""
    rc, out, _ = _git["diff", "--name-only"].run(retcode=None)
    return [l for l in out.splitlines() if l.strip()]


def git_tag(tag_name: str, message: str = "") -> None:
    """Create an annotated tag."""
    if message:
        _git["tag", "-a", tag_name, "-m", message]()
    else:
        _git["tag", tag_name]()


# ─────────────────────────────────────────────────────────────────────────────
# 4. Environment and working directory context managers
# ─────────────────────────────────────────────────────────────────────────────

def run_in_dir(directory: str | Path, cmd_name: str, *args: str) -> str:
    """
    Run a command in a specific working directory.
    local.cwd() is a context manager — restores cwd on exit.
    """
    with local.cwd(str(directory)):
        return run(cmd_name, *args)


def run_with_env(env_vars: dict[str, str], cmd_name: str, *args: str) -> str:
    """
    Run a command with additional environment variables.
    local.env() adds/overrides env vars in the subprocess.
    """
    with local.env(**env_vars):
        return run(cmd_name, *args)


# ─────────────────────────────────────────────────────────────────────────────
# 5. Background processes
# ─────────────────────────────────────────────────────────────────────────────

def run_background(cmd_name: str, *args: str):
    """
    Start a command in the background.
    Returns a Future — call .wait() to block until done.
    cmd & BG starts the process but doesn't wait.
    """
    cmd = local[cmd_name][args]
    return cmd & BG


def run_parallel(commands: list[tuple[str, ...]]) -> list[str]:
    """
    Run multiple commands in parallel using BG futures.
    Collects all results after all complete.
    """
    futures = []
    for cmd_spec in commands:
        cmd_name, *args = cmd_spec
        cmd = local[cmd_name][args]
        futures.append(cmd & BG)

    results = []
    for future in futures:
        future.wait()
        results.append(future.stdout)
    return results


# ─────────────────────────────────────────────────────────────────────────────
# 6. Process execution error handling
# ─────────────────────────────────────────────────────────────────────────────

def safe_run(cmd_name: str, *args: str, default: str = "") -> str:
    """Run a command, returning default string on failure."""
    try:
        return run(cmd_name, *args)
    except ProcessExecutionError:
        return default
    except FileNotFoundError:
        return default


def run_or_raise(cmd_name: str, *args: str, msg: str = "") -> str:
    """Run a command, converting ProcessExecutionError to RuntimeError with context."""
    try:
        return run(cmd_name, *args)
    except ProcessExecutionError as e:
        raise RuntimeError(
            f"{msg or cmd_name} failed (exit {e.retcode}):\n{e.stderr.strip()}"
        ) from e


# ─────────────────────────────────────────────────────────────────────────────
# 7. SSH remote execution (optional — requires paramiko)
# ─────────────────────────────────────────────────────────────────────────────

def run_remote(host: str, cmd_name: str, *args: str, user: str = "", key: str = "") -> str:
    """
    Run a command on a remote host via SSH.
    SshMachine wraps paramiko for transparent remote execution.
    """
    from plumbum.machines import SshMachine
    kwargs: dict[str, Any] = {}
    if user:
        kwargs["user"] = user
    if key:
        kwargs["keyfile"] = key
    with SshMachine(host, **kwargs) as remote:
        cmd = remote[cmd_name][args]
        return cmd()


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== Basic command ===")
    try:
        out = run("uname", "-s")
        print(f"  OS: {out.strip()}")
    except FileNotFoundError:
        print("  uname not available")

    print("\n=== Environment ===")
    try:
        with local.env(MY_VAR="hello_plumbum"):
            out = run("printenv", "MY_VAR")
            print(f"  MY_VAR={out.strip()}")
    except Exception as e:
        print(f"  {e}")

    print("\n=== Git status ===")
    try:
        status = git_status()
        lines = [l for l in status.splitlines() if l.strip()]
        print(f"  {len(lines)} changed file(s)")
    except Exception as e:
        print(f"  {e}")

    print("\n=== Git log (last 3) ===")
    try:
        for commit in git_log(n=3):
            print(f"  {commit}")
    except Exception as e:
        print(f"  {e}")

    print("\n=== safe_run (nonexistent command) ===")
    result = safe_run("nonexistent_binary_12345", default="(not found)")
    print(f"  result: {result!r}")

For the subprocess alternative — subprocess.run(["cmd", "arg"], capture_output=True, text=True) is the stdlib approach but requires building argument lists manually, no pipeline | operator, and verbose boilerplate for chaining; plumbum’s (cmd1 | cmd2)() syntax maps directly to shell pipe semantics, local["cmd"]["-flag"] reads like the original command, and with local.cwd("/path"): / with local.env(VAR="val"): replace shell cd and export without subprocess-level environment dicts. For the sh (python-sh) alternative — python-sh dynamically creates command wrappers as module attributes (from sh import git; git.log()) and uses more magic, while plumbum uses explicit local["git"]["log"]() lookups that are grep-able and IDE-friendly, and plumbum’s SshMachine provides the same API for remote execution — remote["ls"]() — that local["ls"]() provides locally, making it easy to write scripts that optionally run on a remote host. The Claude Skills 360 bundle includes plumbum skill sets covering local[“cmd”]args execution, run() and run_checked() helpers, pipeline operator (cmd1 | cmd2)(), redirection (cmd > file)() and (cmd >> file)(), ProcessExecutionError with retcode/stdout/stderr, run_in_dir with local.cwd() context manager, run_with_env with local.env() context manager, BG background futures, run_parallel multi-process launcher, git_status/log/branch/changed_files helpers, SshMachine remote execution, and safe_run fallback wrapper. Start with the free tier to try shell automation pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free