Skip to content

General

The core of comrun is a CommandRunner class. It's how you run commands and get results. This section covers the basics of using it.

Running a command

Just create a CommandRunner instance and call it with the command you want to run:

from comrun import CommandRunner

runner = CommandRunner()

runner.run('echo "The cake is a lie."')  # (1)!
  1. Prints to stdout:
    The cake is a lie.
    

You can also run commands asynchronously:

async def some_coroutine():
    await runner.run_async('echo "This cake is an async lie."')  # (1)!
  1. Prints to stdout:
    This cake is an async lie.
    

The same runner instance can be reused indefinitely to call other commands. This allows you to invoke multiple commands using the same base configuration, and override individual options on each run as needed (more on this in Configuration section).

Extra CommandRunner trivia
  • CommandRunner instances are callable: __call__ forwards to .run(). So you can do runner("ls -la") instead of runner.run("ls -la"), for example.
  • Runner accepts commands both as a string ("git status") and as an argv (["git", "status"]). Strings are split with shlex.split() internally.

Reading results

Each run returns a CommandResult instance with details about the execution:

result = runner.run("ls")  # (1)!
  1. result is a CommandResult instance with details about this run.

Exit status

There are several ways to check how the command exited:

print(result.exit_code)  # (1)!
print(result.success)  # (2)!
print(result.failure)  # (3)!
  1. Raw exit code (0 means success).
  2. True when exit_code == 0.
  3. True when exit_code != 0.

CommandResult instance is truthy upon success, so you can use it in conditionals:

if result:
    print("Command succeeded!")
else:
    print("Command failed :(")

Captured output

Output of the command is captured in CommandOutput objects. Result has three properties of this type:

print(result.stdout)  # (1)!
print(result.stderr)  # (2)!
print(result.output)  # (3)!
  1. Stdout output.
  2. Stderr output.
  3. Output of stdout + stderr in chronological order.

CommandOutput objects allow you to access their content in several ways:

  1. lines: A tuple of output lines, each without trailing whitespaces/newlines.

    for line in result.stdout.lines:
        print(line)
    

    If you want to process lines right as they come, look into live output hooks.

  2. text: Full output as a single string.

    print(result.stdout.text)
    

    Converting CommandOutput to a str is the same as accessing its text property

    str(output) == output.text
    
    So to print the full captured stdout, you can simply print(result.stdout).

  3. stripped: Full output as a single string, but without leading and trailing whitespaces/newlines.

    print(result.stdout.stripped)
    

  4. value: Same as stripped, but is None if the output is empty.
    if result.stdout.value is not None:
        print("Command produced some output!")
    

These options keep command-output handling short and readable.

Configuration

The strength of comrun is in the ability to create pre-configured runners. There are 3 ways to configure a CommandRunner:

  1. Via constructor arguments:

    base_runner = CommandRunner(quiet=True, check=True) # (1)!
    

    1. This runner will be silent and will raise exceptions on failures.
  2. By modifying an existing runner via .with_options():

    loud_runner = base_runner.with_options(quiet=False) # (1)!
    

    1. This runner will now print its output (quiet=False), but keeps check=True from base_runner.

    .with_options() always creates a new CommandRunner instance, so the original remains unchanged.

  3. By passing options to individual .run() calls:

    loud_runner.run("echo 'Hello, World!'", check=False)  # (1)!
    

    1. This specific run will not raise on failure (check=False), even though loud_runner has check=True.

      It still prints output like loud_runner is configured to do.

    This, of course, works the same with async calls via .run_async().

These methods can be combined as needed to create runners that fit your use case, all while minimizing repeated boilerplate when calling commands.

Available configuration options are listed below.

Working directory (cwd)

Working directory for the command is set with cwd:

runner = CommandRunner(cwd="/path/to/dir")  # (1)!
  1. Commands run via this runner will execute with /path/to/dir as their working directory.

Environment variables (env)

You can override environment variables for the command with env:

runner = CommandRunner(env={"MY_VAR": "value"})  # (1)!
  1. Commands run via this runner will have MY_VAR set to "value" in their environment.

Environment variables set via env replace the entire environment for the command.

To add variables while keeping existing ones, merge with os.environ:

CommandRunner(env={**os.environ, "MY_VAR": "value"})

Silencing output (quiet)

By default, comrun prints the command's output to the console as it appears. You can disable this behavior by passing quiet=True:

runner('echo "Potato."')  # (1)!
runner('echo "POTATO!"', quiet=True)  # (2)!
  1. Prints Potato. to the console.
  2. Prints nothing, no matter how loud the command is. POTATO! is still captured and available in result.stdout.

Raising on failures (check)

By default, comrun will quietly execute the command even if it fails and returns a non-zero exit code. If you want to catch it as an exception instead, you can pass check=True:

result = runner("exit 1")  # (1)!

try:
    runner("exit 1", check=True)  # (2)!
except CommandError as exc:
    print(exc)
  1. Non-zero exits set result.failure to True.
  2. With check=True, non-zero exits raise CommandError.

Read more about CommandError in the Reference section.

Using WSL on Windows (wsl)

On Windows, comrun can automatically prefix commands with wsl for you to run them inside the Windows Subsystem for Linux. This behavior is controlled with the wsl option (default: True on Windows, ignored on POSIX):

windows_runner = CommandRunner(wsl=False)  # (1)!
  1. Commands run via this runner will execute with the native Windows shell instead of WSL (when on Windows).

Encoding (encoding)

By default, comrun decodes command output using the system's preferred encoding. You can override this with the encoding option:

runner = CommandRunner(encoding="utf-8")  # (1)!
  1. Commands run via this runner will decode output using UTF-8.

Others

You can find more configuration options in the Advanced usage and Reference sections.