execute function

Future<int> execute(
  1. String command, {
  2. Stream<List<int>>? stdin,
  3. IOSink? stdout,
  4. IOSink? stderr,
  5. Directory? workingDirectory,
})

Executes a command in a child process shell and returns the exit code.

The command is a shell command line that can include arguments, e.g., echo "Hello world!".

Child stdout/stderr will be forwarded to the parent process. It will use the parent's defaults for stdin/stdout unless overridden with alternative IOSinks.

Parent signals (SIGINT & SIGTERM) will be forwarded to the child, while command is running

If you pass a stdin stream then it will be consumed and forwarded to the child. If you plan on listening to stdin again later, make sure to convert it from a single subscription stream first.

You can specify what workingDirectory the child process should be spawned in. It will default to Directory.current.

Implementation

Future<int> execute(
  final String command, {
  final Stream<List<int>>? stdin,
  IOSink? stdout,
  IOSink? stderr,
  Directory? workingDirectory,
}) async {
  stdout ??= io.stdout;
  stderr ??= io.stderr;
  workingDirectory ??= Directory.current;

  final shell = Platform.isWindows ? 'cmd' : 'bash';
  final shellArg = Platform.isWindows ? '/c' : '-c';

  // NOTE: We invoke a shell instead of the command directly (with runInShell:
  // true). This avoid a lot of edge cases regarding quoting, repeated spaces,
  // etc.
  final process = await Process.start(
    shell,
    [shellArg, command],
    workingDirectory: workingDirectory.path,
  );

  // Forward signals to child process
  final sigSubscription = StreamGroup.merge(
    [
      ProcessSignal.sigint,
      if (!Platform.isWindows) ProcessSignal.sigterm,
    ].map((final s) => s.watch()),
  ).listen((final s) {
    process.kill(s);
  });

  // Forward stdin to the child process
  final stdinSubscription = stdin?.listen(
    process.stdin.add,
    cancelOnError: true,
    onError: (final _) {}, // extremely unlikely, but why not
  );

  // Stream output directly to terminal
  await [
    stdout.addStream(process.stdout),
    stderr.addStream(process.stderr),
  ].wait;
  await stdinSubscription?.cancel();
  await process.stdin.close();
  await sigSubscription.cancel();

  return await process.exitCode;
}