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