Skip to content

Command Execution

fscm provides flexible command execution with output capture, sudo support, and error handling.

Basic Usage

import fscm

# Run a command
fscm.run("echo 'Hello, World!'")

# Capture the result
result = fscm.run("hostname")
print(result.stdout)  # The hostname

The RunReturn Object

Every run() call returns a RunReturn object:

result = fscm.run("ls -la /tmp")

result.ok          # True if exit code is 0
result.returncode  # The exit code (0, 1, etc.)
result.stdout      # Standard output (str or bytes)
result.stderr      # Standard error (str or bytes)
result.to_change   # Convert to CmdRun change object

Checking Success

result = fscm.run("some-command", check=False)

if result.ok:
    print("Success!")
    print(result.stdout)
else:
    print(f"Failed with code {result.returncode}")
    print(result.stderr)

Asserting Success

# Raises exception if command fails
result = fscm.run("critical-command")
result.assert_ok()  # Explicit assertion

# Or use check=True (default)
fscm.run("critical-command", check=True)  # Raises on failure

Parameters

Parameter Type Default Description
cmd str required Command to execute
check bool True Raise on non-zero exit
sudo bool False Run with sudo
quiet bool False Suppress output display
destructive bool True Track in change list
env dict None Environment variables
cwd str None Working directory
stdin str None Input to send
timeout int None Timeout in seconds

Using Sudo

import fscm

# Run with sudo
fscm.run("systemctl restart nginx", sudo=True)

# Set sudo password globally
fscm.settings.sudo_password = "mypassword"
fscm.run("apt-get update", sudo=True)  # Uses the password

Sudo Password Security

Avoid hardcoding passwords. Use environment variables or secrets management:

import os
fscm.settings.sudo_password = os.environ.get("SUDO_PASSWORD")

Output Control

Quiet Mode

# Suppress output display
fscm.run("apt-get update -y", sudo=True, quiet=True)

Streaming Output

# Stream output in real-time (default)
fscm.settings.stream_output = True
fscm.run("make build")  # Output shown as it happens

# Disable streaming
fscm.settings.stream_output = False

Read-Only Commands

For commands that don't modify the system:

import fscm

# run_ro() is quiet and non-destructive by default
result = fscm.run_ro("cat /etc/hostname")
print(result.stdout)

# Equivalent to:
result = fscm.run("cat /etc/hostname", quiet=True, destructive=False)

Checking Command Existence

import fscm

# Check if a command exists
if fscm.run_ro("which docker").ok:
    print("Docker is installed")

# Check if command fails
if fscm.fails("systemctl is-active nginx"):
    print("Nginx is not running")

Getting Output as String

import fscm

# Get stdout directly as string
hostname = fscm.getstdout("hostname")
print(f"Running on {hostname}")

# With sudo
kernel = fscm.getstdout("uname -r", sudo=True)

Running Multiple Commands

import fscm

# Run multiple commands
fscm.runmany("""
    apt-get update
    apt-get install -y nginx
    systemctl enable nginx
""", sudo=True)

# Or as a list
fscm.runmany([
    "apt-get update",
    "apt-get install -y nginx",
    "systemctl enable nginx"
], sudo=True)

Background Execution

import fscm

# Run in background
proc = fscm.run_bg("long-running-task")

# Do other work...

# Check if still running
if proc.poll() is None:
    print("Still running")

# Wait for completion
proc.wait()

Environment Variables

import fscm

# Set environment variables
fscm.run(
    "npm install",
    env={"NODE_ENV": "production", "CI": "true"}
)

Working Directory

import fscm

# Run in specific directory
fscm.run("npm install", cwd="/var/www/myapp")
fscm.run("make build", cwd="/home/user/project")

Providing Input

import fscm

# Send input to command
fscm.run("passwd user", stdin="newpassword\nnewpassword\n", sudo=True)

# Pipe content
fscm.run("mysql mydb", stdin="SELECT * FROM users;")

Timeout

import fscm

# Set timeout (in seconds)
try:
    fscm.run("sleep 100", timeout=5)
except TimeoutError:
    print("Command timed out")

Error Handling

import fscm
from fscm import CmdFailedException

# Default: raises on failure
try:
    fscm.run("false")  # Always fails
except CmdFailedException as e:
    print(f"Command failed: {e}")

# Disable raising
result = fscm.run("false", check=False)
if not result.ok:
    print("Command failed but we handled it")

Destructive vs Non-Destructive

import fscm

# Destructive commands are tracked
fscm.run("rm -rf /tmp/build")  # Recorded in CHANGELIST

# Non-destructive commands are not tracked
fscm.run("ls -la", destructive=False)  # Not recorded

# run_ro() is non-destructive by default
fscm.run_ro("cat /etc/hosts")  # Not recorded

Example: Service Management

import fscm

def manage_service(name: str, action: str):
    """Manage a systemd service."""
    # Check current state
    status = fscm.run_ro(f"systemctl is-active {name}")

    if action == "start" and not status.ok:
        fscm.run(f"systemctl start {name}", sudo=True)
    elif action == "stop" and status.ok:
        fscm.run(f"systemctl stop {name}", sudo=True)
    elif action == "restart":
        fscm.run(f"systemctl restart {name}", sudo=True)

def deploy_and_restart():
    # Deploy new config
    fscm.file("/etc/myapp/config.yaml", new_config)

    # Restart only if config changed
    if any(c.filename == "/etc/myapp/config.yaml" for c in fscm.CHANGELIST):
        manage_service("myapp", "restart")

Example: Build Pipeline

import fscm

def build_project(project_dir: str):
    """Build a project with proper error handling."""
    # Clean previous build
    fscm.run("rm -rf dist/", cwd=project_dir)

    # Install dependencies
    result = fscm.run("npm ci", cwd=project_dir, check=False)
    if not result.ok:
        print("Failed to install dependencies")
        return False

    # Run tests
    result = fscm.run("npm test", cwd=project_dir, check=False)
    if not result.ok:
        print("Tests failed")
        return False

    # Build
    fscm.run("npm run build", cwd=project_dir)

    return True

Next Steps