SH-Office of Shell Commander

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is the Sh library

In many Python projects there is a need to invoke shell commands: manage files, run system processes, interact with installed CLI tools. You can use os.system or subprocess, but they are bulky and require manual handling of arguments, errors, and output.

The Sh library provides a clean, pythonic way to call shell commands as regular Python functions. It greatly simplifies integration of external utilities, makes code readable and easy to debug.

Advantages of using Sh

The Sh library offers several important benefits over standard methods of executing system commands:

Readability and simple syntax – commands look like ordinary Python functions, which makes the code more understandable and maintainable.

Automatic error handling – the library automatically processes return codes and raises exceptions on failures.

Security – protection against command injection thanks to proper argument escaping.

Flexibility – support for pipelines, background execution, streaming I/O and many other features.

Installation and import

Install the library via pip:

pip install sh

Importing it in code requires just one line:

import sh

Core concepts

Sh automatically maps the name of any shell command to a Python function. This allows you to call them naturally:

print(sh.ls("-l"))

Each call returns an object containing stdout, which can be converted to a string, a list of strings, bytes, and other formats.

Basic usage

File operations

# List files
print(sh.ls("-lah"))

# Create a file
sh.touch("file.txt")

# Copy a file
sh.cp("file.txt", "copy.txt")

# Delete a file
sh.rm("copy.txt")

# Create a directory
sh.mkdir("test_dir")

# Move a file
sh.mv("file.txt", "test_dir/")

Text operations

# Show file content
content = sh.cat("file.txt")
print(str(content))

# Search in files
matches = sh.grep("python", "*.py")
for match in matches:
    print(match.strip())

# Count lines
line_count = sh.wc("-l", "file.txt")
print(f"Line count: {line_count.strip()}")

Working with arguments and options

Sh supports various ways to pass command arguments:

Positional arguments

sh.ls("--color", "auto", "/home/user")

Keyword arguments

# Long options
sh.ls(color="auto", all=True, human_readable=True)

# Short options
sh.ls(a=True, l=True, h=True)

Combined approach

# Mixed usage
sh.find("/home", name="*.py", type="f")

Processing command output

Getting the result as a string

output = sh.ls()
print(str(output))

Line‑by‑line processing

for line in output:
    print(line.strip())

Working with bytes

raw_output = sh.ls()
bytes_data = raw_output.stdout

Splitting into a list of strings

lines = str(sh.ls()).split('\n')
for line in lines:
    if line.strip():
        print(line)

Error handling

The Sh library provides powerful mechanisms for dealing with errors:

Basic error handling

try:
    sh.rm("non_existent.txt")
except sh.ErrorReturnCode_1 as e:
    print("Error:", e.stderr.decode())

Handling different exit codes

try:
    result = sh.command_that_might_fail()
except sh.ErrorReturnCode as e:
    print(f"Command exited with code: {e.exit_code}")
    print(f"Stderr: {e.stderr.decode()}")
    print(f"Stdout: {e.stdout.decode()}")

Ignoring specific error codes

# Allow exit codes 0 and 1
sh.grep("pattern", "file.txt", _ok_code=[0, 1])

Integration with pipelines

Sh supports powerful Unix‑style pipeline capabilities:

Simple pipeline

result = sh.grep("python", _in=sh.ls("-l"))
print(result)

Command chain

# Equivalent to: ls -la | grep ".py" | wc -l
count = sh.wc("-l", _in=sh.grep(".py", _in=sh.ls("-la")))
print(f"Number of Python files: {count.strip()}")

Using the pipe operator

# Alternative syntax
result = (sh.ls("-la") | sh.grep(".py") | sh.wc("-l"))
print(result)

Streaming input and output

Sending data to a command

# Send a string
sh.cat(_in="Hello from Python\n")

# Send a file
with open("input.txt", "r") as f:
    sh.sort(_in=f)

Real‑time reading

# Tail a log file in real time
proc = sh.tail("-f", "log.txt", _iter=True)
for line in proc:
    print(line.strip())
    # Add processing logic here

Redirecting output

# Write to a file
sh.ls("-la", _out="output.txt")

# Append to a file
sh.echo("New line", _out="output.txt", _append=True)

Working with custom commands

Creating a command from a path

my_tool = sh.Command("/usr/local/bin/mycli")
result = my_tool("--run", path="/tmp")

Checking command availability

if sh.which("docker"):
    print("Docker is installed")
    docker_version = sh.docker("--version")
    print(docker_version)
else:
    print("Docker not found")

Parallel execution

Background execution

# Run in background
p = sh.sleep(10, _bg=True)
print("Running...")
# Do other work
p.wait()  # Wait for completion

Process management

# Get PID
proc = sh.long_running_command(_bg=True)
print(f"Process PID: {proc.pid}")

# Check status
if proc.is_alive():
    print("Process is still running")

# Force termination
proc.kill()

Working with the environment

Changing the working directory

with sh.pushd("/tmp"):
    sh.touch("test.txt")
    files = sh.ls()
    print(files)
# Automatically returns to the previous directory

Managing environment variables

with sh.env(MY_VAR="123", DEBUG="true"):
    result = sh.printenv("MY_VAR")
    print(result)

Running from a different directory

# Execute a command from a specific cwd
sh.ls(_cwd="/home/user/projects")

Advanced features

Command timeout

try:
    sh.sleep(10, _timeout=2)
except sh.TimeoutException:
    print("Command exceeded time limit")

Working with TTY

# For commands that require a terminal
sh.sudo("apt", "update", _tty_in=True)

Automatic output decoding

# Decode to a string automatically
result = sh.ls(_decode=True)
print(type(result))  # 

Table of main methods and parameters

Method/Parameter Description Example usage
sh.<command>() Invoke a system command sh.ls("-la")
sh.Command(path) Create a command from a file path sh.Command("/bin/custom")
sh.which(cmd) Find a command in PATH sh.which("python3")
sh.pushd(path) Change directory (context manager) with sh.pushd("/tmp"):
sh.env(**vars) Modify environment variables with sh.env(VAR="val"):
_in Pass input data sh.cat(_in="text")
_out Redirect output sh.ls(_out="file.txt")
_err Redirect error stream sh.cmd(_err="errors.txt")
_bg Background execution sh.sleep(10, _bg=True)
_timeout Execution time limit sh.cmd(_timeout=30)
_cwd Working directory sh.ls(_cwd="/home")
_ok_code Allowed exit codes sh.grep("pat", _ok_code=[0,1])
_tty_in Use TTY for input sh.sudo("cmd", _tty_in=True)
_tty_out Use TTY for output sh.cmd(_tty_out=True)
_iter Iterate over lines for line in sh.ls(_iter=True):
_decode Auto‑decode output sh.ls(_decode=True)
_err_to_out Merge stderr into stdout sh.cmd(_err_to_out=True)
_append Append to a file sh.echo("text", _out="f", _append=True)
_long_opts Long options as a dict sh.cmd(_long_opts={"--opt": "val"})
.stdout Access stdout result.stdout
.stderr Access stderr result.stderr
.exit_code Exit status code result.exit_code
.pid Process ID proc.pid
.wait() Wait for completion proc.wait()
.kill() Force termination proc.kill()
.is_alive() Check if running proc.is_alive()

Security and compatibility

Supported platforms

Linux – full feature support
macOS – full Unix‑command support
Windows – limited support via WSL or CMD

Security benefits

The Sh library provides a higher security level compared with alternatives:

  • Automatic argument escaping prevents injection attacks
  • Safe parameter passing without risk of arbitrary code execution
  • Process isolation and lifecycle control

Comparison with other methods

Method API level Security Readability Error handling Flexibility
os.system Low Low Low No Low
subprocess Medium Medium Medium Yes Medium
sh High High Excellent Yes High

Practical usage examples

DevOps automation

import sh

def deploy_application():
    # Update code
    sh.git("pull", "origin", "main")
    
    # Install dependencies
    sh.pip("install", "-r", "requirements.txt")
    
    # Run tests
    try:
        sh.pytest("tests/")
    except sh.ErrorReturnCode as e:
        print(f"Tests failed: {e.stderr.decode()}")
        return False
    
    # Restart service
    sh.systemctl("restart", "myapp")
    return True

System monitoring

def system_health_check():
    # Disk usage
    disk_usage = sh.df("-h")
    print("Disk usage:")
    print(disk_usage)
    
    # Memory usage
    memory = sh.free("-h")
    print("Memory usage:")
    print(memory)
    
    # CPU load
    cpu_load = sh.uptime()
    print("System load:")
    print(cpu_load)

Git operations

def git_operations():
    # Repository status
    try:
        status = sh.git("status", "--porcelain")
        if status.strip():
            print("Uncommitted changes present")
        else:
            print("Working directory clean")
    except sh.ErrorReturnCode:
        print("Not a Git repository")
    
    # Recent commits
    commits = sh.git("log", "--oneline", "-5")
    print("Last 5 commits:")
    print(commits)

Handling common errors

Command not found

try:
    sh.nonexistent_command()
except sh.CommandNotFound as e:
    print(f"Command not found: {e}")

Invalid arguments

try:
    sh.ls("--invalid-option")
except sh.ErrorReturnCode as e:
    print(f"Invalid option: {e.stderr.decode()}")

Timeout exceeded

try:
    sh.long_command(_timeout=10)
except sh.TimeoutException:
    print("Command exceeded time limit")

Best practices

Error handling

Always catch exceptions when invoking commands that may fail:

try:
    result = sh.risky_command()
except sh.ErrorReturnCode as e:
    logger.error(f"Command failed: {e}")
    # Additional error processing

Using context managers

For operations that require environment changes, use context managers:

with sh.pushd("/project"):
    with sh.env(ENV="production"):
        sh.make("deploy")

Command validation

Check for command availability before use:

if not sh.which("docker"):
    raise RuntimeError("Docker is not installed")

Frequently asked questions

How to get only the command’s exit code?

try:
    sh.test_command()
    exit_code = 0
except sh.ErrorReturnCode as e:
    exit_code = e.exit_code

How to run a command with sudo?

sh.sudo("systemctl", "restart", "nginx", _tty_in=True)

How to pass multi‑line text to a command?

text = """
Line 1
Line 2
Line 3
"""
sh.cat(_in=text)

How to get command output as a list of lines?

lines = str(sh.ls("-1")).strip().split('\n')

How to run a command with environment variables?

with sh.env(MY_VAR="value"):
    sh.command_that_uses_env()

Can sh be used in multithreaded applications?

Yes, but with care. Each command spawns a separate process, which is safe for parallel execution.

Limitations and alternatives

Limitations

  • Works only on Unix‑like systems (Linux, macOS)
  • Limited Windows support
  • Not suitable for commands requiring interactive input
  • May be slower than direct subprocess calls for very simple cases

Alternatives

If Sh doesn’t fit your needs, consider:

  • subprocess – lower‑level control
  • plumbum – similar API, alternative library
  • fabric – remote command execution
  • invoke – building CLI applications

Conclusion

Sh is a high‑level library for invoking system commands in Python, offering concise and readable syntax. It is ideal for automation scripts, DevOps tooling, and integrating CLI interfaces into Python applications.

Key advantages of the library:

  • Intuitive Pythonic API
  • Powerful error‑handling capabilities
  • Support for pipelines and streaming I/O
  • Secure command execution
  • Highly configurable execution options

With these features and minimal syntax, Sh makes working with system commands truly pythonic and efficient.

News