Actions Status MetaCPAN Release

NAME

Command::Run - Execute external command or code reference

SYNOPSIS

use Command::Run;

# Simple usage
my $result = Command::Run->new(
    command => ['ls', '-l'],
    stderr  => 'redirect',  # merge stderr to stdout
)->run;
print $result->{data};

# Method chaining style
my $runner = Command::Run->new;
$runner->command('cat', '-n')->with(stdin => $data)->run;
print $runner->data;

# Separate stdout/stderr capture
my $result = Command::Run->new(
    command => ['some_command'],
    stderr  => 'capture',
)->run;
print "data: ", $result->{data};
print "error: ", $result->{error};

# Access output via file descriptor path
my $cmd = Command::Run->new(command => ['date']);
$cmd->update;
system("cat", $cmd->path);  # /dev/fd/N

# Code reference execution
my $result = Command::Run->new(
    command => [\&some_function, @args],
    stdin   => $input_data,
)->run;

# Using with() method
my ($out, $err);
Command::Run->new->command("command", @args)
    ->with(stdin => $input, stdout => \$out, stderr => \$err)
    ->run;

VERSION

Version 0.9903

DESCRIPTION

This module provides a simple interface to execute external commands or code references and capture their output.

When a code reference is passed as the first element of the command array, it is called in a forked child process instead of executing an external command. This avoids the overhead of loading Perl and modules for each invocation.

This module inherits from Command::Run::Tmpfile, which provides temporary file functionality. The captured output is stored in this temporary file, accessible via the path method as /dev/fd/N, which can be used as a file argument to external commands.

CONSTRUCTOR

PARAMETERS

The following parameters can be used with new, with, and run. With new and with, parameters are stored in the object. With run, parameters are temporary and do not modify the object.

METHODS

RETURN VALUE

The run method returns a hash reference containing:

NOFORK AND RAW MODE

Overview

When executing Perl code references, the default fork-based execution has two significant costs:

The nofork option eliminates the fork cost by executing the code reference in the current process. The raw option eliminates the encoding cost by using the :utf8 PerlIO pseudo-layer instead of :encoding(utf8).

Combined, these options can achieve over 30x speedup compared to fork-based execution for lightweight functions with small I/O.

How Nofork Works

In nofork mode, _execute_nofork temporarily redirects the real STDOUT, STDERR, and STDIN file descriptors to temporary files using dup, executes the code reference, then restores them:

# Simplified flow:
open $save, '>&', \*STDOUT;           # save original
open STDOUT, '>&', $tmpfile;          # redirect to tmpfile
$code->(@args);                       # execute code ref
open STDOUT, '>&', $save;             # restore original
$tmpfile->seek(0, 0);
$output = do { local $/; <$tmpfile> }; # read captured output

The code reference sees real STDOUT/STDIN file descriptors (not tied handles), so it behaves identically to the fork path from the callee's perspective. @ARGV, $0, and $_ are protected with local to prevent side effects.

How Raw Mode Works

The raw option controls which PerlIO layer is applied to the temporary files used for I/O redirection:

# Normal mode (raw => 0):
binmode $tmpfile, ':encoding(utf8)';  # full encode/decode

# Raw mode (raw => 1):
binmode $tmpfile, ':utf8';            # flag only, no conversion

In the normal fork path, :encoding(utf8) is necessary because data crosses process boundaries through pipes as byte streams. But in nofork mode, caller and callee share the same Perl interpreter, so Perl's internal string format (which is already UTF-8 internally) can be passed directly. The :utf8 layer simply sets Perl's UTF-8 flag on strings read from the file without performing actual byte-level conversion.

PerlIO Encoding Leak

There is an additional reason to prefer :utf8 over :encoding(utf8) in long-running processes. Repeatedly pushing and popping the :encoding(utf8) layer (which happens on each nofork execution when opening and closing temporary files) causes a cumulative performance degradation in Perl's PerlIO subsystem. This affects all PerlIO operations in the process, not just the ones using the encoding layer.

In benchmarks, nofork with :encoding(utf8) is actually slower than fork after many iterations, due to this leak. Raw mode avoids the issue entirely.

# Benchmark: code ref with stdin (100-byte input, 1000 iterations)
fork:                  399/s (baseline)
nofork + :encoding:    316/s (0.8x — slower than fork!)
nofork + :utf8 (raw): 13,433/s (34x faster)

Zero-Modification Callee Integration

A key advantage of this mechanism is that callee modules typically require no modification to work with nofork+raw mode.

Many Perl modules use use open pragma or equivalent to set up encoding layers on standard I/O:

package App::ansicolumn;
use open IO => ':utf8', ':std';    # sets :encoding(utf8) on STDIO

This works transparently because of execution order. When using nofork mode with method chaining:

require App::ansicolumn;           # (1) module loaded here
Command::Run->new
    ->command(\&ansicolumn, @args)
    ->with(stdin => $text, nofork => 1, raw => 1)
    ->update                       # (2) STDOUT redirected here
    ->data;

At step (1), require loads the module and use open ':std' applies :encoding(utf8) to the original STDOUT. At step (2), _execute_nofork redirects STDOUT to a fresh temporary file with :utf8 layer. The callee's encoding setup has already fired on the original STDOUT and does not affect the redirected one.

This means existing modules like App::ansicolumn and App::ansifold work unchanged with nofork+raw mode, achieving significant speedups with zero code changes on the callee side.

Note: If a module's encoding setup runs lazily (e.g., inside the called function rather than at module load time), the encoding layer would be applied to the redirected STDOUT, conflicting with raw mode. In such cases, the Getopt::EX::raw module can be used to intercept and replace :encoding(utf8) with :raw:utf8 at the callee side.

Caller Protection

Nofork mode executes the code reference in the same process, so care is needed to prevent the callee from corrupting the caller's state. The following protections are applied:

COMPARISON WITH SIMILAR MODULES

There are many modules on CPAN for executing external commands. This module is designed to be simple and lightweight, with minimal dependencies.

This module was originally developed as App::cdif::Command and has been used in production as part of the App::cdif distribution since 2014. It has also been adopted by several unrelated modules, which motivated its release as an independent distribution.

Command::Run differs from these modules in several ways:

SEE ALSO

Command::Run::Tmpfile, IPC::Run, Capture::Tiny, IPC::Run3, Command::Runner

AUTHOR

Kazumasa Utashiro

LICENSE

Copyright 2026 Kazumasa Utashiro.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.