在shell/bash中连接两个命令的输入和输出

11

我有两个(UNIX)程序A和B,它们从stdin/stdout读写。

我的第一个问题是如何将A的stdout连接到B的stdin,并将B的stdout连接到A的stdin。也就是说,像A | B这样的双向管道。我怀疑可以通过使用exec重定向来解决,但我无法使它工作。这些程序是交互式的,因此临时文件不起作用。

第二个问题是,我想复制每个方向并通过日志记录程序将副本传输到stdout,以便我可以看到程序之间传递的(基于文本行的)流量。如果我能解决第一个问题,我可能会用tee >(...)来解决这个问题。

这两个问题似乎应该有众所周知的解决方案,但我还没有找到任何信息。

我更喜欢POSIX shell解决方案,或者至少在cygwin的bash中有效的解决方案。

感谢您的答案,我想出了以下解决方案。A/B命令使用nc监听两个端口。日志记录程序使用sed(带有-u选项进行非缓冲处理)。

bash-3.2$ fifodir=$(mktemp -d)
bash-3.2$ mkfifo "$fifodir/echoAtoB"
bash-3.2$ mkfifo "$fifodir/echoBtoA"
bash-3.2$ sed -u 's/^/A->B: /' "$fifodir/echoAtoB" &
bash-3.2$ sed -u 's/^/B->A: /' "$fifodir/echoBtoA" &
bash-3.2$ mkfifo "$fifodir/loopback"
bash-3.2$ nc -l -p 47002 < "$fifodir/loopback" \
          | tee "$fifodir/echoAtoB" \
          | nc -l -p 47001 \
          | tee "$fifodir/echoBtoA" > "$fifodir/loopback"

这个程序监听端口47001和47002的连接,并将所有数据包通过标准输出进行回显。

在shell 2中执行以下命令:

bash-3.2$ nc localhost 47001
在 shell 3 中执行:
bash-3.2$ nc localhost 47002

现在在 shell 2 中输入的命令将被写入到 shell 3 中,反之亦然,并且流量将被记录到 shell 1 中,类似于:

B->A: input to port 47001
A->B: input to port 47002

以上内容已在Cygwin上进行测试。

更新:上述脚本在几天后停止工作了(!)。显然会出现死锁现象。一些答案中的建议可能更可靠。

8个回答

11

命名管道怎么样?

# mkfifo foo
# A < foo | B > foo
# rm foo

对于你的第二个问题,我认为tee是正确的答案。因此它变成了:

# A < foo | tee logfile | B > foo

5

twinpipe还附带有一个Shell脚本,使用其他人建议的命名管道方法。 - Per Mildner

5

您可能可以使用命名管道:

mkfifo pipe
gawk '$1' < pipe | gawk '$1' > pipe

4
你可以使用Expect工具。

Expect是一种自动化交互应用程序的工具,如telnet、ftp、passwd、fsck、rlogin、tip等。

你可以使用以下代码(取自《探索Expect》一书)作为起点 - 它将proc1的输出连接到proc2的输入,反之亦然,正如您所要求的那样:
#!/usr/bin/expect -f
spawn proc1
set proc1 $spawn_id
spawn proc2
interact -u $proc1

3

我花了很多时间在这个问题上,最终放弃了。后来我决定使用ksh(Korn shell),因为它允许这样做。

cmd1 |& cmd2 >&p <&p

|&是一个管道运算符,用于启动协处理器,&p是该协处理器的文件描述符。


2

我曾经遇到过这个问题,于是我编写了一个简单的C程序。

#include <stdio.h>
#include <unistd.h>

#define PERROR_AND_DIE(_x_) {perror(_x_); _exit(1);}

int main(int argc, char **argv) {
    int fd0[2];
    int fd1[2];


    if ( argc != 3 ) {
        fprintf(stdout, "Usage %s: \"[command 1]\" \"[command 2]\"\n", argv[0]);
        _exit(1);
    }

    if ( pipe(fd0) || pipe(fd1) ) PERROR_AND_DIE("pipe")

    pid_t id = fork();
    if ( id == -1 ) PERROR_AND_DIE("fork");

    if ( id ) {
        if ( -1 == close(0) )  PERROR_AND_DIE("P1: close 0");
        if ( -1 == dup2(fd0[0], 0) ) PERROR_AND_DIE("P1: dup 0"); //Read my STDIN from this pipe

        if ( -1 == close(1) )  PERROR_AND_DIE("P1: close 1");
        if ( -1 == dup2(fd1[1], 1) ) PERROR_AND_DIE("P1: dup 1"); //Write my STDOUT here
        execl("/bin/sh", "/bin/sh", "-c", argv[1], NULL);
        PERROR_AND_DIE("P1: exec")
    }

    if ( -1 == close(0) )  PERROR_AND_DIE("P2: close 0");
    if ( -1 == dup2(fd1[0], 0) ) PERROR_AND_DIE("P2: dup 0");

    if ( -1 == close(1) )  PERROR_AND_DIE("P2: close 1");
    if ( -1 == dup2(fd0[1], 1) ) PERROR_AND_DIE("P2: dup 1");


    execl("/bin/sh", "/bin/sh", "-c", argv[2], NULL);
    PERROR_AND_DIE("P2: exec")
}

1

这个问题类似于我之前提出的一个。其他人提出的解决方案是使用命名管道,但我怀疑你在cygwin中没有它们。目前,我坚持使用我自己尝试的解决方案, 但它需要/dev/fd/0,你可能也没有。

虽然我不太喜欢将命令行传递为字符串的方法(JeeBee(139495)提到了这一点),但在cygwin中这可能是您唯一的选择。


在我看来,这是最好的/唯一合理的解决方案... 不过它似乎需要使用 bash(而不是 dash / sh)。 - Dreamcat4

0
我建议使用“coproc”:
#! /bin/bash
# initiator needs argument

if [ $# -gt 0 ]; then
  a=$1
  echo "Question $a"
else
  read a
fi

if [ $# -gt 0 ]; then
  read a
  echo "$a" >&2
else
  echo "Answer to $a is ..."
fi

exit 0

那么请看这个会话:

$ coproc ./dialog
$ ./dialog  test  < /dev/fd/${COPROC[0]}  > /dev/fd/${COPROC[1]}
Answer to Question test is ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接