bash 到 "interactively" 是否可以通过 stdin/stdout 交替读取和写入子进程?

Is it possible for bash to "interactively" alternate between reading from and writing to a child process via stdin/stdout?

很多语言都问过这个问题,但我还没有找到 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 并且无法决定它的通信方式;它只理解 stdin 和 stdout,因为它旨在供用户交互使用。

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

coproc myProgram 

内置returns fd 对在名为COPROC 的数组中,如果不提供名称则默认。你需要像

这样的东西

写入程序

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   

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

kill "$COPROC_PID"

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

的可执行文件

除了 coproc,您还可以使用 fifo。两个 fifo,一个用于输入,一个用于输出,或者一个 fifo 和一个带有重定向的文件描述符。下面我使用 bash extension >(...) process substitution with a file descriptor and a 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"