后台进程,其中stdin是连接到父shell脚本中fd 3的管道

sshcrbum  于 2023-01-21  发布在  Shell
关注(0)|答案(3)|浏览(102)

bounty将在4天后过期。回答此问题可获得+100声望奖励。zwol希望引起更多人关注此问题。

在可移植shell中,* 不 * 使用命名管道,启动一个后台进程,并让该进程的stdin成为在父shell中的文件描述符3(或其他大于2的数字)上打开的管道,这是可能的吗?另一种说法是,我想在可移植shell中做popen("some-program", "w")在C中做的事情。
具体来说,执行以下代码段的操作,但不使用mkfifo

mkfifo fifo
some-program < fifo &
exec 3> fifo
rm fifo

注意:“bash”标签主要是为了可见性而添加的。我理解这是可能的using the "coprocess" extension in bash, ksh, or zsh;我正在寻找一种在 * 只有 * XPG Issue 6 (POSIX.1-2001) Shell and Utilities工具可用的环境中工作的技术。此外,使用命名管道(最明显的是通过mkfifo)的答案将不会被接受,需要root特权的答案也不会被接受。
不鼓励使用以下任何一种答案,但我会听到一个论点,即没有其中一种或多种答案是不可能做到的:

  • 创建机器语言可执行文件的任何内容(例如c99
  • 主要用于交互式使用的任何内容(例如vi
  • 不属于第6版“Base”规范的任何内容,或在第6版或任何后续版本的POSIX中过时的任何内容(例如uucp

我也会听到一个答案,这个答案会令人信服地证明,在上述限制范围内,这是不可能的。
(Why没有命名管道?因为它们的行为与普通管道不太一样,特别是在旧的、有缺陷的操作系统中,“只使用#!/bin/bash“不是一个选项。此外,在文件系统中暴露管道,无论多么短暂,都意味着不能完全排除一些不相关的进程打开管道的可能性。)

rlcwz9us

rlcwz9us1#

它必须是背景的 * 本身 * 吗?您能容忍不同的父进程吗?

#! /bin/sh

if [ -z "$REINVOKED" ]; then
  # First time we're running.
  # Save our stdout and launch some-program on a pipe connected to
  # our second invocation.  (A new shell will replace this one.)
  exec 4>&1
  REINVOKED=true
  export REINVOKED
  exec /bin/sh -c "$0 | some-program"  # <- WARNING
  exit 1
fi

# Second invocation.
#
exec 3>&1    # dup the pipe (our stdout) to fd3
exec 1>&4    # restore stdout from inherited fd4
exec 4>&-    # close fd4

echo "READY"     # goes to stdout
echo "READY" >&3 # goes to some-program
...

使用环境变量来检测脚本的第二次调用可能会更可靠地作为单独的脚本重新实现。WARNING 行是您需要关注命令注入(CWE-78)的未来修订版本的地方。
进程的祖先将如下所示:

/bin/sh -c "/rather/dodgy.sh | some-program"
   |
   +- /bin/sh -c /rather/dodgy.sh
   |
   +- some-program

它们都在同一个进程组中,可能是从交互式环境(-bash?)中派生出来的。

ylamdve6

ylamdve62#

我不知道问题是什么,但是popen("some-program", "w")只向some-program的stdin提供数据,所以可以用|(用于运行//中的命令)和{ }(用于对多个命令的文件描述符进行分组)来模拟它。

#!/bin/sh
{
    {
        echo "normal output that shouldn't be processed by some-program"
        echo 'feed data to some-program' 1>&3
        # ...
    } 3>&1 1>&4 |
    some-program
} 4>&1
pw136qt2

pw136qt23#

提供创建管道的唯一标准shell特性是|操作符。如果我们假设没有使用扩展,那么规范说明(多命令)管道在子 shell 环境中执行。这样的环境不能修改父 shell 以使管道的写入端的文件描述符在那里可用,所以我们能做的最接近的事情就是让它在管道的写入端的{ compound-command }范围内可用。

#!/bin/sh

exec 4>&1

{
  # Copy the write end of the pipe to FD 3, restore the parent
  # shell's original stdout, and close excess FD 4
  exec 3>&1 1>&4 4>&-
  
  use-fd-3-here
} | something-goes-here

exec 4>&-

现在,虽然它可以被重定向,但异步命令的初始标准输入是(相当于)/dev/null,因此只将some-command &放在管道的读取端是行不通的,但我们可以在some-command周围放置另一个复合命令,以便为从管道复制标准输入文件描述符提供一个位置。例如:

{
  some-command 0>&4 4>&-
} 4>&0 &

这些搭配起来效果很好。例如:

#!/bin/sh

# Copy the standard output FD to preserve access to it within processes in the
# pipeline
exec 4>&1

{
  # Rotate file descriptors:
  #  - the write end of the pipe becomes 3
  #  - the parent shell's standard output becomes 1
  #  - excess FD 4 is closed for tidiness and safety
  exec 3>&1 1>&4 4>&-

  # This is piped into the standard input of a command running asynchronously.
  # In this example, that process will substitute a "!" for the "?" and output
  # the result
  echo 'piped?' 1>&3

  # This does not go to the async process
  echo 'not piped?'
} | {
  # Redirect the read end of the pipe to this process's standard input and
  # clean up the extra file descriptor.
  sed 's/?/!/' 0>&4 4>&-
} 4>&0 &

exec 4>&-

# wait for the async process to terminate
wait

输出:
不是吹笛?
吹笛!
如果您关心4>&0重定向和将/dev/null重定向到异步进程的标准输入的相对顺序,那么多一点复合命令声明就可以使这一点变得明确:

| {
     {
       some-command 's/?/!/' 0>&4 4>&-
     } &
} 4>&0

相关问题