You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Dart中实现类Bash Expect功能:重写Stdin遇阻及方案问询

实现类Bash 'expect'的Dart交互功能

问题背景

我想实现一个等效于Bash expect命令的功能,能够捕获stdout输出并向stdin注入数据。Dart的IOOverrides类支持重写stdout、stdin和stderr,但重写stdin需要一个StdIn实例,而StdIn只有私有构造函数,没法自行实例化,导致无法通过ZoneSpecification重写StdIn。想知道是不是遗漏了什么?既然stdin的getter代码已经支持重写,有没有其他实现方式?

期望最终效果

Interact(spawn: () {
  final age = ask(
    'How old are you',
    defaultValue: '5',
    customPrompt: (prompt, defaultValue, {hidden = false}) =>
        'AAA$prompt:$defaultValue',
  );
  print('You are $age years old');
})
  ..expect('AAAHow old are you:5')
  ..send('6')
  ..expect('You are 6 years old');

可行实现方案

不用纠结实例化StdIn,可以通过以下方式实现:

  • 自定义Mock IO类:创建继承自Stdin和Stdout的子类(两者都是抽象类),重写核心方法:
    • MockStdin:维护一个输入队列,重写readLineSync等方法,从队列中取出预注入的数据返回。
    • MockStdout:维护一个字符串缓冲区,重写write/writeln等方法,把输出内容写入缓冲区。
  • 用IOOverrides包裹执行逻辑:通过IOOverrides.runZoned方法,将目标代码(spawn回调)包裹在自定义的IO环境中,替换默认的stdin和stdout为Mock实现。
  • 封装Interact类:用Interact类管理MockStdin的输入队列和MockStdout的输出缓冲区,实现expect(检查缓冲区是否匹配预期内容)和send(往输入队列添加数据)方法。

核心代码示例

import 'dart:io';
import 'dart:async';
import 'dart:collection';

class MockStdin extends Stdin {
  final Queue<String> _inputQueue = Queue();

  void addInput(String input) {
    _inputQueue.add(input);
  }

  @override
  String? readLineSync({bool? retainNewlines}) {
    if (_inputQueue.isEmpty) return null;
    return _inputQueue.removeFirst();
  }

  @override
  int get available => 0;

  @override
  Stream<List<int>> get asyncExpand => Stream.empty();

  // 其他未实现的Stdin方法可按需补充,或抛出UnsupportedOperationException
}

class MockStdout extends Stdout {
  final StringBuffer _outputBuffer = StringBuffer();

  String get output => _outputBuffer.toString();

  @override
  void write(Object? obj) {
    _outputBuffer.write(obj);
  }

  @override
  void writeln([Object? obj = '']) {
    _outputBuffer.writeln(obj);
  }

  @override
  void writeAll(Iterable objects, [String separator = '']) {
    _outputBuffer.writeAll(objects, separator);
  }
}

class Interact {
  final MockStdin _mockStdin = MockStdin();
  final MockStdout _mockStdout = MockStdout();
  late final Future<void> _runFuture;

  Interact({required void Function() spawn}) {
    _runFuture = IOOverrides.runZoned(() {
      spawn();
      return Future.value();
    }, createStdin: () => _mockStdin, createStdout: () => _mockStdout);
  }

  void expect(String expected) {
    if (!_mockStdout.output.contains(expected)) {
      throw Exception('Expected "$expected" not found in output: "${_mockStdout.output}"');
    }
    // 可选:匹配后清空缓冲区,避免后续重复匹配
    // _mockStdout._outputBuffer.clear();
  }

  void send(String input) {
    _mockStdin.addInput(input);
  }
}

// 示例中的ask函数实现
String ask(String prompt, {String defaultValue = '', String Function(String, String, {bool hidden})? customPrompt}) {
  final promptText = customPrompt?.call(prompt, defaultValue) ?? '$prompt [$defaultValue]: ';
  stdout.write(promptText);
  final input = stdin.readLineSync() ?? defaultValue;
  return input.isNotEmpty ? input : defaultValue;
}

// 使用示例
void main() {
  Interact(spawn: () {
    final age = ask(
      'How old are you',
      defaultValue: '5',
      customPrompt: (prompt, defaultValue, {hidden = false}) =>
          'AAA$prompt:$defaultValue',
    );
    print('You are $age years old');
  })
    ..expect('AAAHow old are you:5')
    ..send('6')
    ..expect('You are 6 years old');
}

内容的提问来源于stack exchange,提问作者Brett Sutton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 09:10:36