# How Fastfetch Executes Arbitrary Shell Commands Using the Command Module

> Learn how fastfetch executes arbitrary shell commands using the Command module. Discover its subprocess spawning, output capturing, and execution modes for efficient command execution.

- Repository: [fastfetch-cli/fastfetch](https://github.com/fastfetch-cli/fastfetch)
- Tags: internals
- Published: 2026-03-30

---

**Fastfetch executes arbitrary shell commands through a dedicated Command module that spawns subprocesses via `ffProcessSpawn`, captures stdout or stderr through `ffProcessReadOutput`, and supports both synchronous and parallel execution modes.**

The fastfetch-cli/fastfetch repository implements custom command execution as a first-class module, allowing users to embed dynamic shell output directly into system information displays. This functionality relies on a clean separation between configuration parsing in [`src/modules/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/command/command.c) and process management in [`src/detection/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/detection/command/command.c).

## Architecture Overview

The Command module splits responsibilities across two distinct layers:

- **[`src/modules/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/command/command.c)** – Parses JSON configuration options, initializes `FFCommandOptions`, and formats the final output for display.
- **[`src/detection/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/detection/command/command.c)** – Handles process spawning, manages the parallel execution queue, and reads command output into memory buffers.

This design allows the main fastfetch loop to prepare commands early (when `parallel` is enabled) while continuing to gather other system metrics, then retrieve results when the module prints.

## Configuration and Option Handling

When fastfetch initializes the Command module, it creates an `FFCommandOptions` structure via `ffInitCommandOptions`. The source code defines platform-specific defaults:

```c
ffStrbufInitStatic(&options->shell,
#if _WIN32
    "cmd.exe"
#else
    "/bin/sh"
#endif);
ffStrbufInitStatic(&options->param,
#if _WIN32
    "/c"
#else
    "-c"
#endif);

```

Users override these defaults through JSON configuration keys including `shell`, `param`, `text`, `useStdErr`, `parallel`, and `splitLines`. The parsing logic resides in `ffParseCommandJsonObject` at lines 44-80 of [`src/modules/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/command/command.c).

## Command Execution Flow

The execution pipeline follows four distinct phases from module invocation to result formatting.

### 1. Module Invocation

`ffPrintCommand` (in `src/modules/command/command.c:6-9`) calls `ffDetectCommand` to obtain the command output. This function serves as the primary entry point for both synchronous and deferred execution.

### 2. Process Spawning

Inside `ffDetectCommand`, the code determines execution strategy based on the `parallel` flag:

- **Synchronous** (`parallel: false`): The command spawns immediately via `spawnProcess` and blocks until completion.
- **Parallel** (`parallel: true`, default): The command is queued in a static FIFO list (`commandQueue`) via `ffPrepareCommand` during fastfetch's initial preparation phase.

The spawning logic in `src/detection/command/command.c:15-27` builds the argument vector for `ffProcessSpawn`:

- With a parameter: `[shell, param, text, NULL]` (e.g., `/bin/sh`, `-c`, `uname -a`)
- Without a parameter: `[shell, text, NULL]`

The `useStdErr` boolean determines whether the helper captures stderr instead of stdout.

### 3. Output Capture

`ffProcessReadOutput` (defined in [`src/common/processing.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/common/processing.c)) collects the child process stream into an `FFstrbuf` buffer. After reading completes, `ffStrbufTrimRightSpace` removes trailing whitespace to ensure clean formatting (see `src/detection/command/command.c:53-59`).

### 4. Result Formatting

Returning to `ffPrintCommand` (lines 20-39), the module either prints the result as a single block or splits it by lines when `splitLines: true`. Each line becomes a separate entry in the fastfetch output.

## Parallel Execution Queue

When `parallel` is enabled, fastfetch optimizes startup latency by calling `ffPrepareCommand` for every Command module during the preparation phase. This function allocates a `FFCommandResultBundle`, adds it to the global `commandQueue`, and returns immediately.

Later, when the main loop reaches the module, `ffDetectCommand` retrieves the first pending bundle using `ffListShift` and returns the pre-captured output. This architecture allows multiple shell commands to run concurrently while fastfetch continues CPU/memory detection (see `src/detection/command/command.c:30-38`).

## Practical Configuration Examples

### Execute System Information Command

```json
{
    "command": [
        {
            "text": "uname -a",
            "splitLines": true
        }
    ]
}

```

This configuration runs `/bin/sh -c "uname -a"` and prints each line of kernel information as a separate fastfetch entry.

### Custom Shell with Stderr Capture

```json
{
    "command": [
        {
            "shell": "/usr/bin/bash",
            "param": "-c",
            "text": "ls /nonexistent 2>&1",
            "useStdErr": true,
            "parallel": false
        }
    ]
}

```

This example overrides the default shell to use Bash, captures the error stream instead of stdout, and forces synchronous execution to guarantee output ordering.

## Programmatic API Usage

For developers embedding fastfetch as a library, the Command module exposes a direct C API:

```c
FFCommandOptions opt;
ffInitCommandOptions(&opt);
ffStrbufSet(&opt.text, "date '+%Y-%m-%d %H:%M:%S'");

FF_STRBUF_AUTO_DESTROY out = ffStrbufCreate();
const char *err = ffDetectCommand(&opt, &out);
if (err) {
    printf("Command failed: %s\n", err);
} else {
    printf("Current time: %s\n", out.chars);
}
ffDestroyCommandOptions(&opt);

```

This snippet mirrors the internal execution flow: initialize options, set the command `text`, invoke `ffDetectCommand`, and handle either the error string or the populated output buffer.

## Summary

- Fastfetch implements command execution through a split architecture separating configuration ([`src/modules/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/command/command.c)) from process management ([`src/detection/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/detection/command/command.c)).
- The module supports both synchronous blocking execution and asynchronous parallel queues via `ffPrepareCommand` and `commandQueue`.
- Process spawning uses `ffProcessSpawn` with configurable shells (`cmd.exe` or `/bin/sh`), parameters (`/c` or `-c`), and output streams (stdout or stderr).
- Output handling relies on `ffProcessReadOutput` and `ffStrbufTrimRightSpace` to capture and clean command results before formatting.
- Users configure commands through JSON options including `text`, `shell`, `useStdErr`, `parallel`, and `splitLines`.

## Frequently Asked Questions

### How does fastfetch handle different shells across operating systems?

According to the source code in [`src/modules/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/command/command.c), fastfetch detects the platform at compile time using `#if _WIN32` preprocessor directives. On Windows, it defaults to `cmd.exe` with the `/c` parameter; on Unix-like systems, it defaults to `/bin/sh` with the `-c` parameter. Users can override these defaults via the `shell` and `param` JSON configuration keys.

### What is the difference between parallel and synchronous command execution?

When `parallel` is set to `true` (the default), fastfetch calls `ffPrepareCommand` during the initial preparation phase to start the shell process immediately, storing the result in a static `commandQueue`. When the module prints, it retrieves the completed output without blocking. When `parallel` is `false`, `ffDetectCommand` calls `spawnProcess` directly and blocks until the command completes, ensuring strict ordering but potentially increasing total execution time.

### Can fastfetch capture stderr instead of stdout from shell commands?

Yes. The `useStdErr` boolean option in `FFCommandOptions` instructs the process helper to capture the standard error stream instead of standard output. This is implemented in [`src/detection/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/detection/command/command.c) where the flag is passed to `ffProcessSpawn`, allowing users to display error messages or diagnostic output within their fastfetch configuration.

### Where does the actual process spawning logic reside?

The low-level process spawning is handled by `ffProcessSpawn` and `ffProcessReadOutput`, defined in [`src/common/processing.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/common/processing.h) and [`src/common/processing.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/common/processing.c). The Command module's detection layer ([`src/detection/command/command.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/detection/command/command.c)) builds the argument vector and invokes these helpers, while the specific platform implementations (POSIX vs Windows) are abstracted within the common processing utilities.