能否让bash通过stdinstdout "交互式 "地交替读取和写入子进程?

问题描述 投票:0回答:1

这个问题已经被问过很多语言,但我还没有找到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

我最初想用 名管 但这是行不通的,因为管道在启动前需要将两端打开,并使用不同的 exec 技巧无法解决这些限制。我并不是排除它们作为一种解决方案,只是指出我没有足够的智慧让它们工作。

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

为了这个问题,我们假设我无法控制 myProgram 而不能规定它的通信方式;它只理解stdin和stdout,因为它的目的是让用户交互使用。

bash stdout stdin
1个回答
3
投票

我想你要找的是 coproc 内置. 它允许你异步运行命令,并为你提供文件描述符来进行交互,例如一对fd,连接到命令的stdin和stdout。

coproc myProgram 

内置的返回数组中的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,因为默认情况下它没有行缓冲。使用 fflush(stdout) 从你的C程序中,或在运行可执行文件时使用 stdbuf -oL


1
投票

而作为替代方案的是 coproc 你可以直接使用一个fifo。可以是两个fifo,一个用于输入,一个用于输出,或者是一个fifo和一个带有重定向的文件描述符。下面我使用bash扩展 >(...) 用文件描述符和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"
© www.soinside.com 2019 - 2024. All rights reserved.