{"id":31780643,"url":"https://github.com/ptlis/shell-command","last_synced_at":"2025-10-10T08:16:58.624Z","repository":{"id":26044930,"uuid":"29488213","full_name":"ptlis/shell-command","owner":"ptlis","description":"A better abstraction for running processes from PHP","archived":false,"fork":false,"pushed_at":"2023-01-12T19:03:14.000Z","size":416,"stargazers_count":22,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-07T03:56:55.156Z","etag":null,"topics":["command","exec","execute","php","process","shell"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"samsung-cnct/container-drunkensmee","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ptlis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-01-19T19:38:45.000Z","updated_at":"2023-12-05T15:13:16.000Z","dependencies_parsed_at":"2023-01-14T03:55:55.965Z","dependency_job_id":null,"html_url":"https://github.com/ptlis/shell-command","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/ptlis/shell-command","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ptlis%2Fshell-command","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ptlis%2Fshell-command/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ptlis%2Fshell-command/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ptlis%2Fshell-command/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ptlis","download_url":"https://codeload.github.com/ptlis/shell-command/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ptlis%2Fshell-command/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002637,"owners_count":26083425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command","exec","execute","php","process","shell"],"created_at":"2025-10-10T08:16:56.762Z","updated_at":"2025-10-10T08:16:58.617Z","avatar_url":"https://github.com/ptlis.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ptlis/shell-command\n\nA developer-friendly wrapper around execution of shell commands.\n\nThere were several goals that inspired the creation of this package:\n\n* Use the [command pattern](https://en.wikipedia.org/wiki/Command_pattern) to encapsulate the data required to execute a shell command, allowing the command to be passed around and executed later.\n* Maintain a stateless object graph allowing (for example) the spawning of multiple running processes from a single command.\n* Provide clean APIs for synchronous and asynchronous usage.\n* Running processes can be wrapped in promises to allow for easy composition.\n\n\n\n[![Build Status](https://api.travis-ci.com/ptlis/shell-command.svg?branch=master)](https://app.travis-ci.com/github/ptlis/shell-command) [![codecov](https://codecov.io/gh/ptlis/shell-command/branch/master/graph/badge.svg?token=tJsLb61LV2)](https://codecov.io/gh/ptlis/shell-command) [![Latest Stable Version](https://poser.pugx.org/ptlis/shell-command/v/stable.png)](https://packagist.org/packages/ptlis/shell-command)\n\n  * [Install](#install)\n  * [Usage](#usage)\n     * [The Builder](#the-builder)\n        * [Set Command](#set-command)\n        * [Set Process Timeout](#set-process-timeout)\n        * [Set Poll Timeout](#set-poll-timeout)\n        * [Set Working Directory](#set-working-directory)\n        * [Add Arguments](#add-arguments)\n        * [Add Raw Arguments](#add-raw-arguments)\n        * [Add Environment Variables](#add-environment-variables)\n        * [Add Process Observers](#add-process-observers)\n        * [Build the Command](#build-the-command)\n     * [Synchronous Execution](#synchronous-execution)\n     * [Asynchronous Execution](#asynchronous-execution)\n        * [Command::runAsynchronous](#commandrunasynchronous)\n     * [Process API](#process-api)\n        * [Process::getPromise](#processgetpromise)\n  * [Mocking](#mocking)\n  * [Contributing](#contributing)\n  * [Known limitations](#known-limitations)\n\n\n\n## Install\n\nFrom the terminal:\n\n```shell\n$ composer require ptlis/shell-command\n```\n\n\n\n## Usage\n\n### The Builder\n\nThe package ships with a command builder, providing a simple and safe method to build commands. \n\n```php\nuse ptlis\\ShellCommand\\CommandBuilder;\n\n$builder = new CommandBuilder();\n```\n\nThe builder will attempt to determine your environment when constructed, you can override this by specifying an environment as the first argument:\n\n```php\nuse ptlis\\ShellCommand\\CommandBuilder;\nuse ptlis\\ShellCommand\\UnixEnvironment;\n\n$builder = new CommandBuilder(new UnixEnvironment());\n```\n\n**Note:** this builder is immutable - method calls must be chained and terminated with a call to ```buildCommand``` like so:\n \n```php\n$command = $builder\n    -\u003esetCommand('foo')\n    -\u003eaddArgument('--bar=baz')\n    -\u003ebuildCommand()\n``` \n\n\n#### Set Command\n\nFirst we must provide the command to execute:\n\n```php\n$builder-\u003esetCommand('git')             // Executable in $PATH\n    \n$builder-\u003esetCommand('./local/bin/git') // Relative to current working directory\n    \n$builder-\u003esetCommand('/usr/bin/git')    // Fully qualified path\n\n$build-\u003esetCommand('~/.script.sh')      // Path relative to $HOME\n```\n\nIf the command is not locatable a ```RuntimeException``` is thrown.\n\n\n#### Set Process Timeout\n\nThe timeout (in microseconds) sets how long the library will wait on a process before termination. Defaults to -1 which never forces termination.\n\n```php\n$builder\n    -\u003esetTimeout(30 * 1000 * 1000)          // Wait 30 seconds\n```\n\nIf the process execution time exceeds this value a SIGTERM will be sent; if the process then doesn't terminate after a further 1 second wait then a SIGKILL is sent.\n\n\n#### Set Poll Timeout\n\nSet how long to wait (in microseconds) between polling the status of processes. Defaults to 1,000,000 (1 second).\n\n```php\n$builder\n    -\u003esetPollTimeout(30 * 1000 * 1000)          // Wait 30 seconds\n\n```\n\n\n#### Set Working Directory\n\nYou can set the working directory for a command:\n\n```php\n$builder\n    -\u003esetCwd('/path/to/working/directory/')\n```\n\n\n#### Add Arguments\n\nAdd arguments to invoke the command with (all arguments are escaped):\n\n```php\n$builder\n    -\u003eaddArgument('--foo=bar')\n```\n\nConditionally add, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddArgument('--foo=bar', $myVar === 5)\n```\n\nAdd several arguments:\n\n```php\n$builder\n    -\u003eaddArguments([\n        '--foo=bar',\n        '-xzcf',\n        'if=/dev/sda of=/dev/sdb'\n    ])\n```\n\nConditionally add, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddArguments([\n        '--foo=bar',\n        '-xzcf',\n        'if=/dev/sda of=/dev/sdb'\n    ], $myVar === 5)\n```\n\n**Note:** Escaped and raw arguments are added to the command in the order they're added to the builder. This accommodates commands that are sensitive to the order of arguments.\n\n\n#### Add Raw Arguments\n\n**WARNING**: Do not pass user-provided data to these methods! Malicious users could easily execute arbitrary shell commands. \n\nArguments can also be applied without escaping:\n\n```php\n$builder\n    -\u003eaddRawArgument(\"--foo='bar'\")\n```\n\nConditionally, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddRawArgument('--foo=bar', $myVar === 5)\n```\n\nAdd several raw arguments:\n\n```php\n$builder\n    -\u003eaddRawArguments([\n        \"--foo='bar'\",\n        '-xzcf',\n    ])\n```\n\nConditionally, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddRawArguments([\n        '--foo=bar',\n        '-xzcf',\n        'if=/dev/sda of=/dev/sdb'\n    ], $myVar === 5)\n```\n\n**Note:** Escaped and raw arguments are added to the command in the order they're added to the builder. This accommodates commands that are sensitive to the order of arguments.\n\n\n#### Add Environment Variables\n\nEnvironment variables can be set when running a command:\n\n```php\n$builder\n    -\u003eaddEnvironmentVariable('TEST_VARIABLE', '123')\n```\n\nConditionally, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddEnvironmentVariable('TEST_VARIABLE', '123', $myVar === 5)\n```\n\nAdd several environment variables:\n\n```php\n$builder\n    -\u003eaddEnvironmentVariables([\n        'TEST_VARIABLE' =\u003e '123',\n        'FOO' =\u003e 'bar'\n    ])\n```\n\nConditionally, depending on the result of an expression:\n\n```php\n$builder\n    -\u003eaddEnvironmentVariables([\n        'TEST_VARIABLE' =\u003e '123',\n        'FOO' =\u003e 'bar'\n    ], $foo === 5)\n```\n\n\n\n#### Add Process Observers\n\nObservers can be attached to spawned processes. In this case we add a simple logger:\n\n```php\n$builder\n    -\u003eaddProcessObserver(\n        new AllLogger(\n            new DiskLogger(),\n            LogLevel::DEBUG\n        )\n    )\n```\n\n\n#### Build the Command\n\nOne the builder has been configured, the command can be retrieved for execution:\n\n```php\n$command = $builder\n    // ...\n    -\u003ebuildCommand();\n```\n\n\n\n### Synchronous Execution\n\nTo run a command synchronously use the ```runSynchronous``` method. This returns an object implementing ```CommandResultInterface```, encoding the result of the command.\n\n```php\n$result = $command\n    -\u003erunSynchronous(); \n```\n\nWhen you need to re-run the same command multiple times you can simply invoke ```runSynchronous``` repeatedly; each call will run the command returning the result to your application.\n\nThe exit code \u0026 output of the command are available as methods on this object:\n\n```php\n$result-\u003egetExitCode();         // 0 for success, anything else conventionally indicates an error\n$result-\u003egetStdOut();           // The contents of stdout (as a string)\n$result-\u003egetStdOutLines();      // The contents of stdout (as an array of lines)\n$result-\u003egetStdErr();           // The contents of stderr (as a string)\n$result-\u003egetStdErrLines();      // The contents of stderr (as an array of lines)\n$result-\u003egetExecutedCommand();  // Get the executed command as a string, including environment variables\n$result-\u003egetWorkingDirectory(); // Get the directory the command was executed in \n```\n\n\n\n### Asynchronous Execution\n\nCommands can also be executed asynchronously, allowing your program to continue executing while waiting for the result.\n\n\n\n#### Command::runAsynchronous\n\nThe ```runAsynchronous``` method returns an object implementing the ```ProcessInterface``` which provides methods to monitor the state of a process.\n\n```php\n$process = $command-\u003erunAsynchronous();\n```\n\nAs with the synchronouse API, when you need to re-run the same command multiple times you can simply invoke ```runAsynchronous``` repeatedly; each call will run the command returning the object representing the process to your application.\n\n### Process API\n\n```ProcessInterface``` provides the methods required to monitor and manipulate the state and lifecycle of a process.\n\nCheck whether the process has completed:\n\n```php\nif (!$process-\u003eisRunning()) {\n    echo 'done' . PHP_EOL;\n}\n```\n\nForce the process to stop:\n\n```php\n$process-\u003estop();\n```\n\nWait for the process to stop (this blocks execution of your script, effectively making this synchronous):\n\n```php\n$process-\u003ewait();\n```\n\nGet the process id (throws a ```\\RuntimeException``` if the process has ended):\n\n```php\n$process-\u003egetPid();\n```\n\nRead output from a stream:\n\n```php\n$stdOut = $process-\u003ereadStream(ProcessInterface::STDOUT);\n```\n\nProvide input (e.g. via STDIN):\n\n```php\n$process-\u003ewriteInput('Data to pass to the running process via STDIN');\n```\n\nGet the exit code (throws a ```\\RuntimeException``` if the process is still running):\n\n```php\n$exitCode = $process-\u003egetExitCode();\n```\n\nSend a signal (SIGTERM or SIGKILL) to the process:\n\n```php\n$process-\u003esendSignal(ProcessInterface::SIGTERM);\n```\n\nGet the string representation of the running command:\n\n```php\n    $commandString = $process-\u003egetCommand();\n```\n\n\n#### Process::getPromise\n\nMonitoring of shell command execution can be wrapped in a [ReactPHP Promise](https://github.com/reactphp/promise). This gives us a flexible execution model, allowing chaining (with [Promise::then](https://github.com/reactphp/promise#promiseinterfacethen)) and aggregation using [Promise::all](https://github.com/reactphp/promise#all), [Promise::some](https://github.com/reactphp/promise#some), [Promise::race](https://github.com/reactphp/promise#race) and their friends.\n \nBuilding promise to execute a command can be done by calling the ```getPromise``` method from a ```Process``` instance. This returns an instance of ```\\React\\Promise\\Promise```:\n\n```php\n$eventLoop = \\React\\EventLoop\\Factory::create();\n\n$promise = $command-\u003erunAsynchonous()-\u003egetPromise($eventLoop);\n```\n\nThe [ReactPHP EventLoop](https://github.com/reactphp/event-loop) component is used to periodically poll the running process to see if it has terminated yet; once it has the promise is either resolved or rejected depending on the exit code of the executed command.\n\nThe effect of this implementation is that once you've created your promises, chains and aggregates you must invoke ```EventLoop::run```:\n\n```php\n$eventLoop-\u003erun();\n```\n\nThis will block further execution until the promises are resolved/rejected.\n\n\n\n\n## Mocking\n\nMock implementations of the Command \u0026 Builder interfaces are provided to aid testing.\n\nBy type hinting against the interfaces, rather than the concrete implementations, these mocks can be injected \u0026 used to return pre-configured result objects.\n\n\n## Contributing\n\nYou can contribute by submitting an Issue to the [issue tracker](https://github.com/ptlis/shell-command/issues), improving the documentation or submitting a pull request. For pull requests i'd prefer that the code style and test coverage is maintained, but I am happy to work through any minor issues that may arise so that the request can be merged.\n\n\n\n\n## Known limitations\n\n* Supports UNIX environments only.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fptlis%2Fshell-command","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fptlis%2Fshell-command","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fptlis%2Fshell-command/lists"}