【问题标题】:Is it possible for bash to "interactively" alternate between reading from and writing to a child process via stdin/stdout?bash 是否可以通过 stdin/stdout 在读取和写入子进程之间“交互地”交替?
【发布时间】:2020-05-13 09:25:14
【问题描述】:

已针对多种语言提出此问题,但我尚未找到 bash 风格的重复项。

假设我有一个程序在写入stdout 和读取stdin 之间交替。

#include <stdio.h>

/*
 * Control_D to exit.
 */
int main(int argc, char** argv){
  char init = 'C';
  if (argc > 1) {
    init = *argv[1];
  }
  putchar(init);
  putchar('\n');

  while (1) {
    int c = getchar();
    if (c == -1) {
      return 0;
    }
    putchar(c);
    putchar('\n');
  }
}

我想编写一个 bash 脚本,它读取程序编写的内容,然后决定编写标准输入的内容,并重复执行此操作。也就是说,像这样:

myProgram &

for i in $(seq 1 10);
do
output=$(# magic command to read myProgram stdout)
if [[ $output = "C" ]]; then
# Magic command to write 'C' to myProgram input
fi
if [[ $output = "D" ]]; then
# Magic command to write 'E' to myProgram input
done

我最初尝试使用named pipes 执行此操作,但这不起作用,因为管道要求在开始之前打开两端并且使用各种exec 技巧无法解决这些限制。我并没有将它们排除在解决方案之外,只是指出我不够聪明,无法让它们发挥作用。

这些神奇的命令是否存在于 bash 中,还是我必须切换到另一种语言?

为了这个问题,我们假设我无法控制myProgram,也无法指定它如何通信;它只理解标准输入和标准输出,因为它旨在供用户以交互方式使用。

【问题讨论】:

  • 在 putchar 之后刷新标准输出时,命名管道工作正常。

标签: bash stdout stdin


【解决方案1】:

我认为您正在寻找coproc builtin。它允许您异步运行命令并为您提供文件描述符以与之交互,即一对 fd,连接到命令的标准输入和标准输出

coproc myProgram 

如果默认情况下没有提供名称,则内置函数返回名为 COPROC 的数组中的 fd 对。你需要类似的东西

写入程序

printf 'foo' >&${COPROC[1]}

从程序中读取

read -u "${COPROC[0]}" var

所以你的整个程序如下所示。假设myprogram 是当前路径中可用的可执行文件。

coproc ./myProgram 

for ((i=1; i<=10; i++)); do
    read -u "${COPROC[0]}" var
    if [[ $var = "C" ]]; then
        printf 'C' >&${COPROC[1]}
    elif [[ $var = "D" ]]; then
        printf 'E' >&${COPROC[1]}
    fi
done   

就像使用&amp; 运行后台作业一样,提供$! 中的进程ID 使用coproc 运行程序会自动更新COPROC_PID 变量中的进程ID,这样您就可以在完成以下操作后执行以下操作程序

kill "$COPROC_PID"

未经测试,但我认为您可能需要清除标准输出,因为它不是默认缓冲的行。在 C 程序中使用 fflush(stdout) 或使用 stdbuf -oL 运行可执行文件

【讨论】:

    【解决方案2】:

    除了coproc,您也可以使用fifo。两个 fifo,一个用于输入,一个用于输出,或者一个 fifo 和一个带有重定向的文件描述符。下面我使用 bash 扩展 &gt;(...) 进程替换与文件描述符和 fifo:

    f=/tmp/fifo.fifo
    mkfifo "$f"
    exec 10> >( { echo "Header!"; sed 's/^/process: /'; } >"$f" )
    
    IFS= read -r first_line <"$f"
    echo "First line: $first_line"
    # "First line: Header!"
    
    echo 123 >&10
    IFS= read -r second_line <"$f"
    echo "Second line: $second_line"
    # Second line: process: 123
    
    exec 10<&-
    rm "$f"
    

    所以你的程序可能看起来像:

    f=/tmp/fifo.fifo
    mkfifo "$f"
    exec 10> >(myProgram >"$f")
    
    for i in $(seq 1 10); do
        IFS= read -r output <"$f"
        if [[ $output = "C" ]]; then
              echo "C" >&10
        fi
        if [[ $output = "D" ]]; then
             echo "D" >&10
        fi
    done
    
    exec 10<&-
    rm "$f"
    

    【讨论】:

    • 如我所写,当我复制并粘贴第一块代码时,脚本在输出First line: Header!后永远被阻塞
    • 第二个例子似乎不起作用,但我可能将它错误地翻译成我的上下文。
    猜你喜欢
    • 2022-11-25
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 2011-05-05
    • 2017-02-01
    • 1970-01-01
    • 2017-08-16
    • 2017-08-18
    相关资源
    最近更新 更多