Skip to content

Latest commit

 

History

History
165 lines (112 loc) · 5.49 KB

File metadata and controls

165 lines (112 loc) · 5.49 KB

Shell

Introduction

The Shell component provides the ability to run shell commands in a subprocess, in a non-blocking manner.

?> The Shell component serves as replacement for shell_exec(), exec(), system() functions.

!> The Shell component is not a replacement for amphp/process, or symfony/process.

Usage

use Psl\Shell;

$result = Shell\execute('echo', arguments: ['Hello, World!']); // Hello, World!

API

Functions

  • [Shell\execute(string $command, list<string> $arguments = [], ?string $working_directory = null, array<string, string> $environment = [], bool $escape_arguments = true, ?float $timeout = null): string php]

    Execute an external program.

    • [$command php]: The command to execute.
    • [$arguments php]: The command arguments listed as separate entries.
    • [$working_directory php]: The initial working directory for the command.
    • [$environment php]: A dict with the environment variables for the command that will be run.
    • [$escape_arguments php]: If set to true ( default ), all [$arguments php] will be escaped using [Shell\escape_argument(...) php].
    • [$timeout php]: The timeout in seconds. If set to null, the timeout will be disabled.

    If the command returns a non-zero exit code, [Shell\Exception\FailedExecutionException php] will be thrown.
    If the command being run is suspicious ( e.g: contains NULL byte ), [Shell\Exception\PossibleAttackException php] will be thrown.
    If the [$working_directory php] doesn't exist, or unable to create a new process, [Shell\Exception\RuntimeException php] will be thrown.
    If the [$timeout php] is reached before being able to read the process stream, [Shell\Exception\TimeoutException php] will be thrown.

    use Psl\Shell;
    
    $result = Shell\execute('echo', arguments: ['Hello, World!'], timeout: 1.0); // Hello, World!

    [Shell\execute(...) php] waits for the underlying process output in a non-blocking manner, this means that while a process is running, the script will not block, and allows other I/O operations to be performed.

    This non-blocking nature is useful when you want to execute a lot of commands concurrently, and you don't want to block the script.

    use Psl\Async;
    use Psl\Shell;
    
    // execute `sleep 1` 4 times concurrently
    Async\concurrently([
      fn() => Shell\execute('sleep', arguments: ['1']),
      fn() => Shell\execute('sleep', arguments: ['1']),
      fn() => Shell\execute('sleep', arguments: ['1']),
      fn() => Shell\execute('sleep', arguments: ['1']),
    ]);

    All commands in the example above will be executed concurrently, and the script will finish in ~1 second.

    We can also set a timeout for the read operation, and if the process is still running after the timeout, [Shell\Exception\TimeoutException php] will be thrown.

    use Psl\Shell;
    
    try {
      Shell\execute('sleep', ['1'], timeout: 0.5);
    } catch (Shell\Exception\TimeoutException $e) {
      // The command timed out.
    }
  • [Shell\escape_argument(string $argument): string php]

    Escape a command argument.

    This function should only be used in combination with [Shell\execute(...) php], as the escape format is specific to the [Shell\execute(...) php] function.

    • [$argument php]: The command argument to escape.
    use Psl\Shell;
    
    $arguments = [
      $bar, // intentionally not escaped
      Shell\escape_argument($baz),
      Shell\escape_argument($qux),
    ];
    
    $result = Shell\execute($foo, arguments: $arguments, escape_arguments: false);

Exceptions

  • [final class Shell\Exception\FailedExecutionException implements Shell\Exception\ExceptionInterface extends Execption php]

    Thrown when the command returns a non-zero exit code.

    use Psl\Shell;
    
    try {
      Shell\execute('php', arguments: ['-r', 'exit(1);'], timeout: 1.0);
    } catch (Shell\Exception\FailedExecutionException $e) {
      $exit_code = $e->getCode();
      $stdout_content = $e->getOutput();
      $stderr_content = $e->getErrorOutput();
    
      // ... do something.
    }
  • [final class Shell\Exception\PossibleAttackException implements Shell\Exception\ExceptionInterface extends Execption php]

    Thrown when the command being run is suspicious ( e.g: contains NULL byte ).

    use Psl\Shell;
    
    try {
      Shell\execute('echo', arguments: ["Hello, World!\0"]);
    } catch (Shell\Exception\PossibleAttackException $e) {
      // ... do something.
    }
  • [final class Shell\Exception\RuntimeException implements Shell\Exception\ExceptionInterface extends Execption php]

    Thrown when the [$working_directory php] doesn't exist, or unable to create a new process.

    use Psl\Shell;
    
    try {
      Shell\execute('echo', arguments: ['Hello, World!'], working_directory: '/foo/bar');
    } catch (Shell\Exception\RuntimeException $e) {
      // ... do something.
    }
  • [final class Shell\Exception\TimeoutException implements Shell\Exception\ExceptionInterface extends Execption` php]

    Thrown when the [$timeout php] is reached before being able to read the process stream.

    use Psl\Shell;
    
    try {
      Shell\execute('sleep', ['1'], timeout: 0.5);
    } catch (Shell\Exception\TimeoutException $e) {
      // ... do something.
    }