How Fastfetch Executes Arbitrary Shell Commands Using the Command Module
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 and process management in src/detection/command/command.c.
Architecture Overview
The Command module splits responsibilities across two distinct layers:
src/modules/command/command.c– Parses JSON configuration options, initializesFFCommandOptions, and formats the final output for display.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:
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.
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 viaspawnProcessand blocks until completion. - Parallel (
parallel: true, default): The command is queued in a static FIFO list (commandQueue) viaffPrepareCommandduring 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) 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
{
"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
{
"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:
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) from process management (src/detection/command/command.c). - The module supports both synchronous blocking execution and asynchronous parallel queues via
ffPrepareCommandandcommandQueue. - Process spawning uses
ffProcessSpawnwith configurable shells (cmd.exeor/bin/sh), parameters (/cor-c), and output streams (stdout or stderr). - Output handling relies on
ffProcessReadOutputandffStrbufTrimRightSpaceto capture and clean command results before formatting. - Users configure commands through JSON options including
text,shell,useStdErr,parallel, andsplitLines.
Frequently Asked Questions
How does fastfetch handle different shells across operating systems?
According to the source code in 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 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 and src/common/processing.c. The Command module's detection layer (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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →