Python subprocess replacement that allows calling system commands as Python functions
—
Advanced process control including background execution, process monitoring, signal handling, and process lifecycle management. Provides comprehensive control over long-running commands and process interactions.
Manage the lifecycle of running processes with methods to wait, terminate, and kill processes.
class RunningCommand:
def wait(self):
"""
Wait for the process to complete.
Returns:
None
Raises:
ErrorReturnCode: If process exits with non-zero status
"""
def kill(self):
"""
Forcefully kill the process with SIGKILL.
Returns:
None
"""
def terminate(self):
"""
Gracefully terminate the process with SIGTERM.
Returns:
None
"""Usage examples:
import sh
import time
# Start a long-running process
proc = sh.sleep(30, _bg=True)
# Check if we need to cancel it
time.sleep(5)
if some_condition:
proc.terminate() # Try graceful termination first
time.sleep(2)
if proc.is_alive():
proc.kill() # Force kill if still running
# Wait for normal completion
try:
proc.wait()
print("Process completed normally")
except sh.ErrorReturnCode as e:
print(f"Process failed: {e}")Monitor process status and retrieve process information.
class RunningCommand:
def is_alive(self) -> bool:
"""
Check if the process is still running.
Returns:
bool: True if process is running, False otherwise
"""
@property
def pid(self) -> int:
"""Process ID of the running command."""
@property
def exit_code(self) -> int:
"""Exit code of the process (None if still running)."""Usage examples:
import sh
import time
# Monitor multiple background processes
processes = [
sh.ping("-c", "10", "google.com", _bg=True),
sh.wget("http://example.com/large-file.zip", _bg=True),
sh.rsync("-av", "/source/", "/dest/", _bg=True)
]
# Monitor all processes
while any(proc.is_alive() for proc in processes):
for i, proc in enumerate(processes):
if proc.is_alive():
print(f"Process {i} (PID {proc.pid}) still running...")
else:
print(f"Process {i} completed with exit code {proc.exit_code}")
time.sleep(5)
print("All processes completed")Send signals to running processes for advanced process control.
class RunningCommand:
def signal(self, sig):
"""
Send a signal to the process.
Parameters:
- sig: int or signal constant (e.g., signal.SIGUSR1)
Returns:
None
"""Usage examples:
import sh
import signal
import time
# Start a process that handles signals
proc = sh.tail("-f", "/var/log/system.log", _bg=True)
# Let it run for a while
time.sleep(10)
# Send a custom signal (if the process handles it)
proc.signal(signal.SIGUSR1)
# Send SIGTERM for graceful shutdown
proc.signal(signal.SIGTERM)
# Wait a bit, then force kill if needed
time.sleep(2)
if proc.is_alive():
proc.signal(signal.SIGKILL)Manage process groups and sessions for complex process hierarchies.
def __call__(self, *args, _new_session=False, **kwargs):
"""
Execute command with process group control.
Parameters:
- _new_session: bool = True to start in new session
Returns:
RunningCommand: Process object
"""Usage examples:
import sh
# Start process in new session (detached from parent)
daemon_proc = sh.python("daemon.py", _bg=True, _new_session=True)
# This process will continue even if parent script exits
print(f"Daemon started with PID {daemon_proc.pid}")
# The daemon is now independent of this script's lifecycleHandle process timeouts and long-running command management.
def __call__(self, *args, _timeout=None, **kwargs):
"""
Execute command with timeout.
Parameters:
- _timeout: int/float = seconds to wait before killing process
Returns:
str: Command output
Raises:
TimeoutException: If command exceeds timeout
"""Usage examples:
import sh
# Set timeout for potentially hanging commands
try:
# Network command that might hang
result = sh.curl("http://slow-server.com", _timeout=30)
print("Download completed:", result)
except sh.TimeoutException:
print("Download timed out after 30 seconds")
# Background process with timeout monitoring
proc = sh.sleep(60, _bg=True)
start_time = time.time()
timeout = 10
while proc.is_alive():
if time.time() - start_time > timeout:
print("Manually timing out process")
proc.terminate()
break
time.sleep(1)Monitor and control process resources.
class RunningCommand:
@property
def process(self):
"""Access to underlying subprocess.Popen object."""Usage examples:
import sh
import psutil # External library for extended process info
# Get detailed process information
proc = sh.find("/", "-name", "*.log", _bg=True)
# Access underlying process for advanced control
popen = proc.process
print(f"Process memory usage: {popen.memory_info()}")
# Use with psutil for extended monitoring
if hasattr(psutil, 'Process'):
ps_proc = psutil.Process(proc.pid)
print(f"CPU usage: {ps_proc.cpu_percent()}")
print(f"Memory usage: {ps_proc.memory_info()}")Manage multiple related processes as a group.
import sh
import time
class ProcessManager:
def __init__(self):
self.processes = []
def start(self, command, *args, **kwargs):
"""Start a process and add to management."""
proc = command(*args, _bg=True, **kwargs)
self.processes.append(proc)
return proc
def wait_all(self):
"""Wait for all processes to complete."""
for proc in self.processes:
proc.wait()
def kill_all(self):
"""Kill all running processes."""
for proc in self.processes:
if proc.is_alive():
proc.kill()
def status(self):
"""Get status of all processes."""
running = sum(1 for p in self.processes if p.is_alive())
total = len(self.processes)
return f"{running}/{total} processes running"
# Usage
manager = ProcessManager()
# Start multiple related processes
manager.start(sh.rsync, "-av", "/data1/", "/backup/")
manager.start(sh.rsync, "-av", "/data2/", "/backup/")
manager.start(sh.rsync, "-av", "/data3/", "/backup/")
# Monitor progress
while any(p.is_alive() for p in manager.processes):
print(manager.status())
time.sleep(5)
print("All backups completed")Install with Tessl CLI
npx tessl i tessl/pypi-sh