A simple object oriented interface to execute shell commands in PHP

Overview

php-shellcommand

GitHub Tests Packagist Version Packagist Downloads GitHub license Packagist PHP Version Support

php-shellcommand provides a simple object oriented interface to execute shell commands.

Installing

Prerequisites

Your php version must be 5.4 or later.

Installing with composer

This package can be installed easily using composer.

composer require mikehaertl/php-shellcommand

Features

  • Catches stdOut, stdErr and exitCode
  • Handle argument escaping
  • Pass environment vars and other options to proc_open()
  • Pipe resources like files or streams into the command
  • Timeout for execution

Examples

Basic Example


use mikehaertl\shellcommand\Command;

// Basic example
$command = new Command('/usr/local/bin/mycommand -a -b');
if ($command->execute()) {
    echo $command->getOutput();
} else {
    echo $command->getError();
    $exitCode = $command->getExitCode();
}

Advanced Features

Add Arguments

addArg('--keys', array('key1','key2'));">

$command = new Command('/bin/somecommand');
// Add arguments with correct escaping:
// results in --name='d'\''Artagnan'
$command->addArg('--name=', "d'Artagnan");

// Add argument with several values
// results in --keys key1 key2
$command->addArg('--keys', array('key1','key2'));

Pipe Input Into Command

From string:

execute()) { echo $command->getError(); } else { echo $command->getOutput(); } // Output: // { // "foo": 0 // }">

$command = new ('jq') // jq is a pretty printer
$command->setStdIn('{"foo": 0}');
if (!$command->execute()) {
    echo $command->getError();
} else {
    echo $command->getOutput();
}
// Output:
// {
//   "foo": 0
// }

From file:


$fh = fopen('test.json', 'r');
// error checks left out...
$command = new Command('jq');
$command->setStdIn($fh);
if (!$command->execute()) {
    echo $command->getError();
} else {
    echo $command->getOutput();
}
fclose($fh);

From URL:


$fh = fopen('https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m,relativehumidity_2m,windspeed_10m', 'r');
// error checks left out...
$command = new Command('jq');
$command->setStdIn($fh);
if (!$command->execute()) {
    echo $command->getError();
} else {
    echo $command->getOutput();
}
fclose($fh);

Set Command Instance Options


// Create command with options array
$command = new Command(array(
    'command' => '/usr/local/bin/mycommand',

    // Will be passed as environment variables to the command
    'procEnv' => array(
        'DEMOVAR' => 'demovalue'
    ),

    // Will be passed as options to proc_open()
    'procOptions' => array(
        'bypass_shell' => true,
    ),
));

API

Properties

  • $escapeArgs: Whether to escape any argument passed through addArg(). Default is true.
  • $escapeCommand: Whether to escape the command passed to setCommand() or the constructor. This is only useful if $escapeArgs is false. Default is false.
  • $useExec: Whether to use exec() instead of proc_open(). This is a workaround for OS which have problems with proc_open(). Default is false.
  • $captureStdErr: Whether to capture stderr when useExec is set. This will try to redirect the otherwhise unavailable stderr to stdout, so that both have the same content on error. Default is true.
  • $procCwd: The initial working dir passed to proc_open(). Default is null for current PHP working dir.
  • $procEnv: An array with environment variables to pass to proc_open(). Default is null for none.
  • $procOptions: An array of other_options for proc_open(). Default is null for none.
  • $nonBlockingMode: Whether to set the stdin/stdout/stderr streams to non-blocking mode when proc_open() is used. This allows to have huge inputs/outputs without making the process hang. The default is null which will enable the feature on Non-Windows systems. Set it to true or false to manually enable/disable it. Note that it doesn't work on Windows.
  • $timeout: The time in seconds after which the command should be terminated. This only works in non-blocking mode. Default is null which means the process is never terminated.
  • $locale: The locale to (temporarily) set with setlocale() before running the command. This can be set to e.g. en_US.UTF-8 if you have issues with UTF-8 encoded arguments.

You can configure all these properties via an array that you pass in the constructor. You can also pass command, execCommand and args as options. This will call the respective setter (setCommand(), setExecCommand(), etc.).

Methods

  • __construct($options = null)
    • $options: either a command string or an options array (see setOptions())
  • __toString(): The result from getExecCommand()
  • setOptions($options): Set command options
    • $options: array of name => value options that should be applied to the object. You can also pass options that use a setter, e.g. you can pass a command option which will be passed to setCommand().
  • setCommand($command): Set command
    • $command: The command or full command string to execute, like gzip or gzip -d. You can still call addArg() to add more arguments to the command. If $escapeCommand was set to true, the command gets escaped through escapeshellcmd().
  • getCommand(): The command that was set through setCommand() or passed to the constructor.
  • getExecCommand(): The full command string to execute.
  • setArgs($args): Set argument as string
    • $args: The command arguments as string. Note, that these will not get escaped. This will overwrite the args added with addArgs().
  • getArgs(): The command arguments that where set through setArgs() or addArg(), as string
  • addArg($key, $value=null, $escape=null): Add argument with correct escaping
    • $key: The argument key to add e.g. --feature or --name=. If the key does not end with and =, the (optional) $value will be separated by a space. The key will get escaped if $escapeArgs is true.
    • $value: The optional argument value which will get escaped if $escapeArgs is true. An array can be passed to add more than one value for a key, e.g. addArg('--exclude', array('val1','val2')) which will create the option "--exclude 'val1' 'val2'".
    • $escape: If set, this overrides the $escapeArgs setting and enforces escaping/no escaping
  • setStdIn(): String or resource to supply to command via standard input. This enables the same functionality as piping on the command line. It can also be a resource like a file handle or a stream in which case its content will be piped into the command like an input redirection.
  • getOutput(): The command output as string. Empty if none.
  • getError(): The error message, either stderr or internal message. Empty if no error.
  • getStdErr(): The stderr output. Empty if none.
  • getExitCode(): The exit code or null if command was not executed.
  • getExecuted(): Whether the command was successfully executed.
  • getIsWindows(): Whether we are on a Windows Owe are on a Windows OS
  • execute(): Executes the command and returns true on success, false otherwhise.

Note: getError(), getStdErr() and getOutput() return the trimmed output. You can pass false to these methods if you need any possible line breaks at the end.

Comments
  • PHP hanging indefinitely

    PHP hanging indefinitely

    I was testing some html conversions using the mikehaertl/phpwkhtmltopdf, when I noticed one made PHP hang indefinitely.

    Doing some debug, I found out that it was happening in the php-shellcommand/src/Command.php file at line number 317:

    $this->_stdOut = stream_get_contents($pipes[1]); 
    

    After some research, I found a similar problem in this stackoverflow question and an apparent solution. http://stackoverflow.com/questions/31194152/proc-open-hangs-when-trying-to-read-from-a-stream

    According to the stackoverflow answer, there is a possibility of an execution not outputting anything to stdout (pipe[1]) when an error occurs, so stream_get_contents waits for a stream that never comes.

    As pointed out in the stackoverflow answer, reading the stderr (pipe[2]) before stdout (pipe[1]) did the trick for me, with the PHP finishing execution and the error message being outputted.

    $this->_stdErr = stream_get_contents($pipes[2]);
    $this->_stdOut = stream_get_contents($pipes[1]); 
    

    My system config:

    • Ubuntu 14.04.1 LTS running as a headless VM on Virutalbox
    • PHP Version 5.5.22-1+deb.sury.org~trusty+1
    • Apache/2.4.12
    • wkhtmltopdf 0.12.2 (with patched qt)
    opened by leopcastro 27
  • Passing a command with spaces on Windows doesn't work

    Passing a command with spaces on Windows doesn't work

    Sorry for moving the thread around, I got really confused with all the code. When I try passing this as an exaple:

     '..\..\..\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe http://localhost/Ponudomat/printRdy.php print.pdf'
    

    The program returns this:

    '..\..\..\Program' is not recognized as an internal or external command, operable program or batch file.
    

    It separates the DIR into different commands because of the spaces, to fix it you need to pass the DIR within the "" and that's where the problem kicks in.

    When I try:

     '"..\..\..\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe" http://localhost/Ponudomat/printRdy.php print.pdf'
    

    I just keep getting the same error as the one above. When I try adding "/ instead of " to try and force php into making a quote within a string. This is the confusing part, when I put:

     '"/..\..\..\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"/ http://localhost/Ponudomat/printRdy.php print.pdf'
    

    It actually runs the command and partially works. So now I got the .exe running but it gives me an error as it cannot read the parameters it needs because of the "/ so I get:

    Loading pages (1/6) [> ] 0% [===> ] 5% [======> ] 10% Error: Failed loading page file:///C:/ (sometimes it will work just to ignore this error with --load-error-handling ignore) [========> ] 14% [=========> ] 15% [==========> ] 17% [============> ] 20% [=================================> ] 55% Exit with code 1 due to network error: ContentOperationNotPermittedError
    

    Because the command is as follows getCommand:

    /..\..\..\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"/ http://localhost/Ponudomat/printRdy.php print.pdf
    
    bug windows-only 
    opened by DomiQ 14
  • Command hangs forever after update from 1.2.2 to 1.2.3

    Command hangs forever after update from 1.2.2 to 1.2.3

    Hanging test: https://github.com/dmstr/phd5-app/blob/master/tests/codeception/cli/DbDumpCept.php

    Output:

    Cli Tests (5) ------------------------------------------------------------------
    ✔ 1-InitCept:  (4.90s)
    ✔ AuditCept:  (0.10s)
    make: *** [run-tests] Error 137
    stdin: is not a tty
    ERROR: Build failed: execution took longer than 3600 seconds
    

    It's a rather simple DB-dump which takes <1s in the test setup.

    Works fine after reverting to 1.2.2

    opened by schmunk42 11
  • Can't run composer on Centos 7

    Can't run composer on Centos 7

    When i tried run this code:

    $command = new Command(ROOT_PATH. '/composer/ composer update');
    $result = $command->execute())
    

    I get this result: sh: /var/www/mysite.com/public_html/composer/: Is a directory

    opened by AeolusIt 9
  • fgets and stream_set_blocking add `@` for disable warning output

    fgets and stream_set_blocking add `@` for disable warning output

    https://github.com/mikehaertl/php-shellcommand/blob/6c6f44cee9bef0d5e7670852d04128745de455ac/src/Command.php#L407-L408

    https://github.com/mikehaertl/php-shellcommand/blob/6c6f44cee9bef0d5e7670852d04128745de455ac/src/Command.php#L463-L466

    opened by Jamlee 8
  • wkhtmltopdf processes becomes zombie

    wkhtmltopdf processes becomes zombie

    In our code that uses mikehaertl/phpwkhtmltopdf we have an option: ['window-status'=>'ready-to-print']

    However on some occasions ready-to-print is not set in js. In this case wkhtmltopdf processes will never stop. And after some time we have hundreds of zombie-processes. Is it possible to set max execution time for wkhtmltopdf?

    https://github.com/mikehaertl/phpwkhtmltopdf/issues/223

    P.S.: ini_set('max_execution_time', 15); Should do the job to kill php process and wkhtmltopdf with it right? It does not.

    28378 www 1 20 0 213M 31292K piperd 5 0:01 0.00% php-fpm 28588 www 1 20 0 213M 31304K piperd 0 0:01 0.00% php-fpm

    It hangs on piperead. max_execution_time apperantly does not work for piperead.


    wkhtmltopdf -V wkhtmltopdf 0.12.4 (with patched qt)

    mikehaertl/php-shellcommand 1.2.4
    mikehaertl/php-tmpfile 1.1.0
    mikehaertl/phpwkhtmltopdf 2.2.1

    PHP 7.0.13 (php-fpm)

    opened by drmax24 8
  • Update composer.json

    Update composer.json

    A straightforward change. The goal of this PR is to have a php version requirement (I chose the lowest one from the CI setup) and also to install phpunit locally as a dev dependency.

    opened by Arzaroth 6
  • More secure keys addArg()

    More secure keys addArg()

    Case: $command->addArg('; ls -la', 'something'); Generated command: cmd ; ls -la 'something' Сan execute any command on the server.

    May be can do escapeshellarg for keys too.

    opened by 4n70w4 5
  • Executing a file with a relative path on Windows is broken

    Executing a file with a relative path on Windows is broken

    If I pass in a relative path on Windows to execute (assuming what I want to execute is already in the working directory or in my PATH environment variable), it produces incorrect output.

    For example:

    mysqldump --defaults-extra-file=C:\temp\my.cnf --add-drop-table --comments --create-options --dump-date --no-autocommit --routines --set-charset --triggers --result-file=C:\temp\backup.sql testdatabase
    

    Will produce:

    cd "mysqldump --defaults-extra-file=C:\temp\my.cnf --add-drop-table --comments --create-options --dump-date --no-autocommit --routines --set-charset --triggers --result-file=C:\temp" && backup.sql testdatabase
    

    It looks like https://github.com/mikehaertl/php-shellcommand/commit/d9a0e8ecff24badfe07129630a67c3d19e31a094 is really only a fix for absolute paths.

    opened by angrybrad 5
  • Issue with getIsWindows() change.

    Issue with getIsWindows() change.

    The recent added check in the $descriptors array for windows has broken functionality for me. Running on a linux machine. Previously had the 'a' descriptor, but now with the getIsWindows() check on line 286, it's returning false, giving a 'w' descriptor. If I am building a small pdf this doesn't seem to be a problem, but for larger pdfs it just hangs. I have temporarily commented out the change here returning it to using the 'a', and everything seems to be in order again.

    opened by epicwally 5
  • Don't trim stdout and stderr

    Don't trim stdout and stderr

    I'm using this library to test the output of a shell script. My script returns a newline at the end of the output. The trim() calls remove this newline, thus causing my tests to fail:

    I think this library should keep the output as-is and let the user determine whether or not to trim the output.

    opened by colinodell 5
  • proc_open hangs - for few options (--orientation

    proc_open hangs - for few options (--orientation "landscape")

    $this->_stdErr = stream_get_contents($pipes[2]); $this->_stdOut = stream_get_contents($pipes[1]);

    Environment: Windows XAMPP

    Have to swap these two lines, https://stackoverflow.com/questions/31194152/proc-open-hangs-when-trying-to-read-from-a-stream

    opened by sujith-entreda 1
  • Issues with proc_* functions and PHP '--enable-sigchild' configure command option

    Issues with proc_* functions and PHP '--enable-sigchild' configure command option

    Hi,

    It looks like PHP compiled with '--enable-sigchild' will have issues with php-shellcommand when proc_* functions are used (by default). php-shellcommand will return an error when the command executes successfully.

    The reason is discussed in the following PHP bug reports: https://bugs.php.net/bug.php?id=61873 https://bugs.php.net/bug.php?id=71804

    There is no issue when using useExec option on a system with PHP compiled with '--enable-sigchild'.

    There is not much that can be done other than detecting the '--enable-sigchild' configure command option and adding the useExec option. Unfortunately, there doesn't appear to be any way to do this aside from parsing phpinfo().

    Is there any other way to handle this issue?

    Thanks.

    opened by maxguru 3
  • Support the ability to output to console while the command getting executed

    Support the ability to output to console while the command getting executed

    Currently if a command is getting executed and that takes long then we will never know if it hangs, or still running, even if we want to show the progress it is hard to implement. My PR is addressing this feature, basically we make execute() simulate the behavior of real command gets executed in shell, while still running, the output of command will be echo to console.

    opened by hAbd0u 0
  • Ideas for addArg

    Ideas for addArg

    It seems that addArg is doing more than it should, which is causing the code to be a little complicated. In my use case, I'm doing one of a few things, which vary greatly on how the logic needs to behave:

    • Basic key/value pairs - --file, $tarball
    • Arguments only, no values - --extract
    • Values only - $filename
    • Crazy redirects - -O > $destination

    (If it wasn't obvious, my test case here is tar.) Since there's a lot going on, but not a lot of flexibility, would it make sense to have a few different methods for addArg, for various use cases.

    As a list critique point, passing in '--file=', $tarball looks weird, I feel moving the option for a separator to an argument could make more sense.

    Either way, I'm going to monkey patch a local copy I grabbed for a project I'm working on, and can submit my results if you're interested. Otherwise great looking class, the convenience of it handling the necessary proc_* operations is useful.

    opened by cdp1337 8
Releases(1.6.4)
Owner
Michael Härtl
Michael Härtl
Termage provides a fluent and incredibly powerful object-oriented interface for customizing CLI output text color, background, formatting, theming and more.

Termage provides a fluent and incredibly powerful object-oriented interface for customizing CLI output text color, background, formatting, theming and

TERMAGE 75 Dec 20, 2022
A developer-friendly wrapper around execution of shell commands.

ptlis/shell-command A developer-friendly wrapper around execution of shell commands. There were several goals that inspired the creation of this packa

brian ridley 18 Dec 31, 2022
An object-oriented option parser library for PHP, which supports type constraints, flag, multiple flag, multiple values, required value checking

GetOptionKit Code Quality Versions & Stats A powerful option parser toolkit for PHP, supporting type constraints, flag, multiple flag, multiple values

Yo-An Lin 140 Sep 28, 2022
Web Shell Detector – is a php script that helps you find and identify php/cgi(perl)/asp/aspx shells.

Web Shell Detector – is a php script that helps you find and identify php/cgi(perl)/asp/aspx shells. Web Shell Detector has a “web shells” signature database that helps to identify “web shell” up to 99%. By using the latest javascript and css technologies, web shell detector has a light weight and friendly interface.

Maxim 763 Dec 27, 2022
YAPS - Yet Another PHP Shell

YAPS - Yet Another PHP Shell Yeah, I know, I know... But that's it. =) As the name reveals, this is yet another PHP reverse shell, one more among hund

Nicholas Ferreira 60 Dec 14, 2022
I gues i tried to make a shell that's looks like a terminal in single php file

php-shell-gui Terms of service This tool can only be used for legal purposes. You take full responsibility for any actions performed using this. The o

Squar3 4 Aug 23, 2022
ReactPHP Shell, based on the Symfony Console component.

Pecan Event-driven, non-blocking shell for ReactPHP. Pecan (/pɪˈkɑːn/) provides a non-blocking alternative to the shell provided in the Symfony Consol

Michael Crumm 43 Sep 4, 2022
An Interactive Shell to Lumen Framework.

ABANDONED Please consider to use the official Laravel Tinker, it is also compatible with Lumen: laravel/tinker Lumen Artisan Tinker An Interactive She

Vagner Luz do Carmo 112 Aug 17, 2022
Simple but yet powerful library for running almost all artisan commands.

:artisan gui Simple but yet powerful library for running some artisan commands. Requirements Laravel 8.* php ^7.3 Installation Just install package: c

null 324 Dec 28, 2022
[ABANDONED] PHP library for executing commands on multiple remote machines, via SSH

#Shunt Inspired by Ruby's Capistrano, Shunt is PHP library for executing commands on multiple remote machines, via SSH. Specifically, this library was

The League of Extraordinary Packages 436 Feb 20, 2022
PHP library for executing commands on multiple remote machines, via SSH

#Shunt Inspired by Ruby's Capistrano, Shunt is PHP library for executing commands on multiple remote machines, via SSH. Specifically, this library was

The League of Extraordinary Packages 436 Feb 20, 2022
Library for creating CLI commands or applications

Console Motivation: this library purpose is to provide a lighter and more robust API for console commands and/or applications to symfony/console. It c

Théo FIDRY 16 Dec 28, 2022
Handle signals in artisan commands

Using this package you can easily handle signals like SIGINT, SIGTERM in your Laravel app.

Spatie 96 Dec 29, 2022
A package built for lumen that ports most of the make commands from laravel.

A package built for lumen that ports most of the make commands from laravel. For lumen v5.1, but will most likely work for 5.2 as well. I haven't tested. If you have requests, let me know, or do it yourself and make a pull request

Michael Bonds 22 Mar 8, 2022
Supercharge your Symfony console commands!

zenstruck/console-extra A modular set of features to reduce configuration boilerplate for your commands: /** * Creates a user in the database. * *

Kevin Bond 29 Nov 19, 2022
🤖 GitHub Action to run symfony console commands.

Symfony Console GitHub Action Usage You can use it as a Github Action like this: # .github/workflows/lint.yml name: "Lint" on: pull_request: push

Nucleos 3 Oct 20, 2022
Helper commands for Laravel Beyond Crud

About beyond-crud-helpers This package has many helper commands for the Laravel BEyond CRUD project by Spatie Installation composer require --dev tarr

null 4 Mar 8, 2022
A PocketMine-MP plugin which allows the users to edit no permission message of commands

CommandPermissionMessage A PocketMine-MP plugin which allows the users to edit no permission message of commands Have you ever got bored by the red me

cosmicnebula200 3 May 29, 2022
A Cli tool to save you time, and gives you the power to scaffold all of your models,controllers,commands

A Cli tool to save you time, and gives you the power to scaffold all of your models,controllers,commands... at once Installation You can install the p

Coderflex 16 Nov 11, 2022