【问题标题】:redirecting stdin _and_ stdout to a pipe将标准输入和标准输出重定向到管道
【发布时间】:2011-06-23 14:36:05
【问题描述】:

我想运行一个程序“A”,让它的输出进入另一个程序“B”的输入,以及标准输入进入“B”的输入。如果程序“A”关闭,我希望“B”继续运行。

我可以轻松地将 A 输出重定向到 B 输入:
./a | ./b

如果我愿意,我可以将 stderr 合并到输出中:
./a 2>&1 | ./b

但我不知道如何将标准输入结合到输出中。我的猜测是:
./a 0>&1 | ./b
但它不起作用。

这是一个不需要我们重写任何测试程序的测试:

$ echo ls 0>&1 | /bin/sh -i
$ a  b  info.txt
$
/bin/sh: Cannot set tty process group (No such process)

如果可能的话,我想在命令行上只使用 bash 重定向来做到这一点(我不想编写一个 C 程序来分叉子进程,并且每次我想要做一些 stdin 重定向时都做任何复杂的事情到管道)。

【问题讨论】:

  • 也许tee 命令会对您有所帮助。祝你好运。
  • 当我想到它时,@markie,我想知道你想做什么。在这种情况下,采购脚本(例如. ~/script.sh)会做你想做的事吗?
  • @Arthur 这是在玩 linux 编程游戏时出现的。 overthewire.org/wargames/vortex我还没有做到很远,但是到目前为止有两个级别遇到了这个问题。基本上,如果你给程序一个正确的值,它会使用 exec() 或类似的方法给你一个 shell “/bin/sh -i”。出于某种原因,bash 的输入像这样重定向,这真的很奇怪。如果在我提供更多输入之前它就用完了输入,它可能会表现得很奇怪,这会导致笨拙的黑客攻击axtaxt.wordpress.com/2010/12/28/overthewire-vortex-level10-2 我感觉我们缺少一个明显的解决方案。

标签: linux shell redirect io-redirection


【解决方案1】:

如果不编写辅助程序,这是无法做到的。

通常,stdin 可以是只读文件描述符(哎呀,它可能指的是只读文件)。所以你不能“插入”任何东西。

您需要编写一个“帮助”程序来监视两个文件描述符(例如,0 和 3),以便从这两个文件描述符中读取并“合并”它们。一个简单的selectpoll 循环就足够了,你可以用大多数脚本语言编写它,但不是shell,我不认为。

然后您可以使用 shell 重定向将程序的输出提供给“帮助程序”的描述符 3。

既然你想要的基本上是“tee”的反义词,我可以称之为“eet”...

[编辑]

要是你能在后台启动“猫”就好了……

但这会失败,因为带有控制终端的后台进程无法从标准输入中读取。因此,如果您可以将“猫”从其控制终端中分离出来并在后台运行它...

在 Linux 上,“setsid cat”应该大致做到这一点。但是(a)我不能让它很好地工作,(b)我今天真的没有时间做这个,(c)无论如何它都是非标准的。

我只会编写帮助程序。

[编辑 2]

好的,这似乎可行:

{ seq 5 ; sleep 2 ; seq 5 ; } | /bin/bash -c 'set -m ; setsid cat ; echo HELLO'

set -m 事物强制 bash 启用作业控制,这显然是防止 shell 从 /dev/null 重定向标准输入所必需的。

这里,echo HELLO 代表您的“程序 A”。 seq 命令(中间有sleep)只是提供一些输入。是的,您可以将整个事情通过管道传递给进程 B。

关于你可以要求的丑陋和不可移植的解决方案......

【讨论】:

  • 我认为即使是背景cat 也不会同时读取多个描述符。 (如果不需要同时进行,根据我的回答,前台使用就可以解决问题。)我希望 socat 有一些有用的东西,但它没有。
  • @Arthur:是的,但是在后台运行“cat”为您将标准输入复制到标准输出,然后脚本可以继续将自己的数据写入标准输出或运行执行此操作的子进程。
  • 我明白了.. 使用 cat 获取标准输入的输入并将其作为输出回显。一旦转换为输出,理论上它应该能够通过管道传输到某个地方。好主意。让我们忽略这里的同时性问题,那么可以做到吗?出于某种原因,以下基于此的想法并不完全奏效。
  • @Arthur @markie:我添加了第二个更新,其中包含似乎可以解决问题的构造。但这很可怕。
  • @Nemo 我认为在后台你不能指望stdouts 是一样的,但你似乎已经让它工作了......以一种丑陋的方式。我想我宁愿拥有 C 程序!
【解决方案2】:

管道有两端。一个是写的,写出来的出现在另一端,是读的。

这是一个管道,而不是 T 形或 Y 形接头。

我认为您的情况是不可能的。让“stdin 输入”任何东西都没有意义。

【讨论】:

  • 我能够将 stdout 和 stderr 都附加到管道上,这没有问题,因此可以连接多个东西。就像您也可以将多个内容重定向到同一个文件一样。而且我不明白您为什么抱怨“标准输入要输入”。也许我的英语不好。当我运行“cat”时,stdin 将进入 cat 的输入。这是否更好地解释了我所说的“stdin 将输入”的意思?现在我希望 stdin 也可以从管道中出来以连接到程序的输入。
【解决方案3】:

要使给定的测试用例工作,您必须在 sh -c '...' 构造中从控制终端设备 /dev/tty 输入 while ... read

注意eval 的使用(这里可以避免吗?)input> 上的多行命令将失败。

echo 'ls; export var=myval'  | ( 
stdin="$(</dev/stdin)"
/bin/sh -i -c '
  eval "$1"; 
  while IFS="" read -e -r -p "input> " line; do 
    history -s "${line}"
    eval "${line}"; 
  done </dev/tty
' argv0 "${stdin}"
)

input> echo $var

对于类似的问题和命名管道的使用见这里:

BASH: Best architecture for reading from two input streams

【讨论】:

    【解决方案4】:

    这不能完全按照所示完成,但要执行您的示例,您可以利用 cat 将文件连接在一起的能力:

    cat <(echo ls) - | /bin/sh
    

    (您可以执行 -i,但您必须让另一个进程终止 /bin/sh,因为您尝试 Ctrl-C 和 Ctrl-D 退出将失败。)

    这假设您想传入管道输入,然后从标准输入接受。你也可以让它在标准输入完成后做一些事情,或者在两边做一些事情——但它不会逐个字符或逐行合并输入。

    【讨论】:

    • 这是个好主意,但行为很奇怪。当我按“向上”时,我看不到 shell 提示符或以前的命令。但我可以输入和运行命令。要退出,我需要输入“exit”并按两次回车。是什么导致了这些奇怪的事情?
    • 哦,使用 -i 我可以看到 shell 提示符,但它并没有真正响应我输入的内容(Ctrl C 使它再次打印 shell 提示符,输入或任何命令都被忽略)。什么!?
    • @markie:没有 -i,shell 不是交互式的,因此它以更适合脚本的模式运行。脚本不需要提示,也不需要通过“up”执行先前的命令。实际上,我不确定退出方面,因为我没有观察到这一点。使用 -i 它试图连接到终端,但问题是管道不像终端那样运行——例如,Ctrl-C 不产生信号,而是作为另一个字符传递以进行处理。跨度>
    • @markie:有关交互式 bash 的更多细节:gnu.org/software/bash/manual/… -- 请注意,包括我的在内的许多解决方案都可能是特定于 bash 的!
    【解决方案5】:

    如果我正确理解您的要求,您希望进行此设置(ASCII 艺术放在首位):

    o----+----->|  A   |----+---->|  B  |---->o
         |                  ^
         |                  |
         +------------------+
    

    如果流程 A 关闭商店,流程 B 应该能够继续将输入流发送到 B。

    正如您所意识到的,这是一个非标准设置,只能通过使用辅助程序将输入驱动到 A 和 B 来实现。您最终会遇到一些有趣的同步问题,但它会非常好地工作只要你的消息足够短。

    实现这一点所需的管道是值得注意的 - 您需要两个管道,一个用于 A 的输入,另一个用于 B 的输入,并且 A 的输出也将连接到 B 的输入。

    o---->|  C  |---------->|  A   |----+---->|  B  |---->o
             |                          ^
             |                          |
             +--------------------------+
    

    请注意,C 将两次写入数据,一次写入 A,一次写入 B。还要注意,从 A 到 B 的管道与从 C 到 A 的管道是同一管道。

    【讨论】:

      【解决方案6】:

      这似乎是你想要的:

      $ ( ./a

      (我不清楚您是否希望 a 获得输入...此解决方案将所有输入发送到 b) 当然,在这种情况下,b 的输入是严格排序的:a 的所有输出 首先发送到 b,然后 a 终止,然后输入到 b。如果你想要东西 交错,试试:

      $ ( ./a

      【讨论】:

      • 这是一个好主意,但我用问题中给出的测试用例尝试了你的解决方案,但我无法让它工作。 "(echo ls; cat) | /bin/sh -i" 似乎给了我一个 shell,我可以输入,但它不接受我输入的任何内容。这是怎么回事?
      最近更新 更多