fix: never use a shell
This commit is contained in:
17
README.md
17
README.md
@@ -10,23 +10,16 @@ use Nih\CommandBuilder\Stdio;
|
|||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
$child = (new Command('/usr/bin/cat'))
|
$child = (new Command('cat'))
|
||||||
->stdin(Stdio::piped())
|
->stdin(Stdio::piped())
|
||||||
->spawn(shell: false);
|
->stdout(Stdio::piped())
|
||||||
|
->spawn();
|
||||||
|
|
||||||
$child->stdin?->write('Hello, this is pretty cool.');
|
$child->stdin?->write('Hello, this is pretty cool.');
|
||||||
$child->stdin?->close();
|
|
||||||
|
|
||||||
$output = $child->output();
|
$output = $child->output();
|
||||||
|
|
||||||
var_dump($output)
|
echo $output->stdout;
|
||||||
|
|
||||||
// object(Nih\CommandBuilder\Output)#4 (3) {
|
// Hello, this is pretty cool.
|
||||||
// ["stdout"]=>
|
|
||||||
// string(27) "Hello, this is pretty cool."
|
|
||||||
// ["stderr"]=>
|
|
||||||
// string(0) ""
|
|
||||||
// ["code"]=>
|
|
||||||
// int(0)
|
|
||||||
// }
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,17 +8,6 @@ $output = (new Command('echo'))
|
|||||||
->arg('Hello, World!')
|
->arg('Hello, World!')
|
||||||
->output();
|
->output();
|
||||||
|
|
||||||
var_dump($output);
|
echo $output->stdout;
|
||||||
|
|
||||||
// object(Nih\CommandBuilder\Output)#9 (3) {
|
// Hello, World!
|
||||||
// ["stdout"]=>
|
|
||||||
// string(14) "Hello, World!
|
|
||||||
// "
|
|
||||||
// ["stderr"]=>
|
|
||||||
// string(0) ""
|
|
||||||
// ["code"]=>
|
|
||||||
// object(Nih\CommandBuilder\ExitStatus)#8 (1) {
|
|
||||||
// ["code"]=>
|
|
||||||
// int(0)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ $echo = (new Command('echo'))
|
|||||||
->stdout(Stdio::piped())
|
->stdout(Stdio::piped())
|
||||||
->spawn();
|
->spawn();
|
||||||
|
|
||||||
$cat = (new Command('cat'))
|
(new Command('cat'))
|
||||||
->stdin(Stdio::stream($echo->stdout))
|
->stdin(Stdio::stream($echo->stdout))
|
||||||
->status();
|
->status();
|
||||||
|
|
||||||
// Prints "Hello, World!\n"
|
// Hello, World!
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ final class Command implements Stringable
|
|||||||
{
|
{
|
||||||
public readonly string $program;
|
public readonly string $program;
|
||||||
|
|
||||||
private array $args;
|
private array $args = [];
|
||||||
private ?array $environment = null;
|
private ?array $environment = null;
|
||||||
private bool $environmentInherit = true;
|
private bool $environmentInherit = true;
|
||||||
private ?string $cwd = null;
|
private ?string $cwd = null;
|
||||||
@@ -36,6 +36,8 @@ final class Command implements Stringable
|
|||||||
*
|
*
|
||||||
* Builder methods are provided to change these defaults and otherwise
|
* Builder methods are provided to change these defaults and otherwise
|
||||||
* configure the process.
|
* configure the process.
|
||||||
|
*
|
||||||
|
* If program is not an absolute path, the `PATH` will be searched.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $program)
|
public function __construct(string $program)
|
||||||
{
|
{
|
||||||
@@ -44,7 +46,6 @@ final class Command implements Stringable
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->program = $program;
|
$this->program = $program;
|
||||||
$this->args = [$this->program];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,6 +66,11 @@ final class Command implements Stringable
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* To pass multiple arguments see {@see Command::args()}.
|
* To pass multiple arguments see {@see Command::args()}.
|
||||||
|
*
|
||||||
|
* Note that the arguments are not passed through a shell, but given
|
||||||
|
* literally to the program. This means that shell syntax like quotes,
|
||||||
|
* escaped characters, word splitting, glob patterns, variable substitution,
|
||||||
|
* etc. have no effect.
|
||||||
*/
|
*/
|
||||||
public function arg(string|Stringable $arg): static
|
public function arg(string|Stringable $arg): static
|
||||||
{
|
{
|
||||||
@@ -77,6 +83,11 @@ final class Command implements Stringable
|
|||||||
*
|
*
|
||||||
* To pass a single argument see {@see Command::arg()}.
|
* To pass a single argument see {@see Command::arg()}.
|
||||||
*
|
*
|
||||||
|
* Note that the arguments are not passed through a shell, but given
|
||||||
|
* literally to the program. This means that shell syntax like quotes,
|
||||||
|
* escaped characters, word splitting, glob patterns, variable substitution,
|
||||||
|
* etc. have no effect.
|
||||||
|
*
|
||||||
* @param iterable<string|Stringable> $args
|
* @param iterable<string|Stringable> $args
|
||||||
*/
|
*/
|
||||||
public function args(iterable $args): static
|
public function args(iterable $args): static
|
||||||
@@ -259,9 +270,9 @@ final class Command implements Stringable
|
|||||||
*
|
*
|
||||||
* @param bool $shell Run the command with or without a shell
|
* @param bool $shell Run the command with or without a shell
|
||||||
*/
|
*/
|
||||||
public function spawn(bool $shell = true): Child
|
public function spawn(): Child
|
||||||
{
|
{
|
||||||
return $this->spawnWithDescriptorSpec($shell, [
|
return $this->spawnWithDescriptorSpec([
|
||||||
$this->stdin instanceof Stdio
|
$this->stdin instanceof Stdio
|
||||||
? $this->stdin->descriptorSpec
|
? $this->stdin->descriptorSpec
|
||||||
: STDIN,
|
: STDIN,
|
||||||
@@ -282,9 +293,9 @@ final class Command implements Stringable
|
|||||||
*
|
*
|
||||||
* @param bool $shell Run the command with or without a shell
|
* @param bool $shell Run the command with or without a shell
|
||||||
*/
|
*/
|
||||||
public function status(bool $shell = true): ExitStatus
|
public function status(): ExitStatus
|
||||||
{
|
{
|
||||||
return $this->spawn($shell)->wait();
|
return $this->spawn()->wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,9 +307,9 @@ final class Command implements Stringable
|
|||||||
*
|
*
|
||||||
* @param bool $shell Run the command with or without a shell
|
* @param bool $shell Run the command with or without a shell
|
||||||
*/
|
*/
|
||||||
public function output(bool $shell = true): Output
|
public function output(): Output
|
||||||
{
|
{
|
||||||
return $this->spawnWithDescriptorSpec($shell, [
|
return $this->spawnWithDescriptorSpec([
|
||||||
$this->stdin instanceof Stdio
|
$this->stdin instanceof Stdio
|
||||||
? $this->stdin->descriptorSpec
|
? $this->stdin->descriptorSpec
|
||||||
: ['pipe', 'r'],
|
: ['pipe', 'r'],
|
||||||
@@ -326,7 +337,7 @@ final class Command implements Stringable
|
|||||||
*/
|
*/
|
||||||
public function getArgs(): array
|
public function getArgs(): array
|
||||||
{
|
{
|
||||||
return array_slice($this->args, 1);
|
return $this->args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,11 +367,15 @@ final class Command implements Stringable
|
|||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return implode(' ', array_map(escapeshellarg(...), $this->args));
|
return implode(' ', [
|
||||||
|
escapeshellarg($this->program),
|
||||||
|
...array_map(escapeshellarg(...), $this->args)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function spawnWithDescriptorSpec(bool $shell, array $descriptorSpec): Child
|
private function spawnWithDescriptorSpec(array $descriptorSpec): Child
|
||||||
{
|
{
|
||||||
|
// Validate stream resources in descriptor spec.
|
||||||
foreach ($descriptorSpec as $descriptor => $spec) {
|
foreach ($descriptorSpec as $descriptor => $spec) {
|
||||||
if (!is_array($spec) && !is_resource($spec)) {
|
if (!is_array($spec) && !is_resource($spec)) {
|
||||||
throw new CommandException(sprintf(
|
throw new CommandException(sprintf(
|
||||||
@@ -371,17 +386,29 @@ final class Command implements Stringable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($shell) {
|
// Find executable if path is not absolute.
|
||||||
$command = (string) $this;
|
$program = $this->program;
|
||||||
} else if (is_executable($this->program)) {
|
if ($program[0] !== DIRECTORY_SEPARATOR) {
|
||||||
$command = $this->args;
|
$path = getenv('PATH');
|
||||||
} else {
|
if (is_string($path)) {
|
||||||
|
foreach (explode(':', $path) as $path) {
|
||||||
|
$path = $path . '/' . $program;
|
||||||
|
if (is_executable($path)) {
|
||||||
|
$program = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_executable($program)) {
|
||||||
throw new CommandException(sprintf(
|
throw new CommandException(sprintf(
|
||||||
'Program "%s" is not executable',
|
'Program "%s" is not executable',
|
||||||
$this->program,
|
$program,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle environment inheritance.
|
||||||
$environment = $this->environment;
|
$environment = $this->environment;
|
||||||
if (is_array($environment) && $this->environmentInherit) {
|
if (is_array($environment) && $this->environmentInherit) {
|
||||||
foreach (getenv() as $key => $val) {
|
foreach (getenv() as $key => $val) {
|
||||||
@@ -391,7 +418,14 @@ final class Command implements Stringable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$proc = proc_open($command, $descriptorSpec, $pipes, $this->cwd, $environment);
|
$proc = proc_open(
|
||||||
|
[$program, ...$this->args],
|
||||||
|
$descriptorSpec,
|
||||||
|
$pipes,
|
||||||
|
$this->cwd,
|
||||||
|
$environment,
|
||||||
|
);
|
||||||
|
|
||||||
if ($proc === false) {
|
if ($proc === false) {
|
||||||
throw new CommandException(sprintf(
|
throw new CommandException(sprintf(
|
||||||
'Program "%s" failed to start',
|
'Program "%s" failed to start',
|
||||||
|
|||||||
Reference in New Issue
Block a user