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



在不使用命名管道的情况下,在可移植shell中,是否可以启动后台进程,并使该进程的stdin成为在父shell中的文件描述符3(或大于2的其他数字(上打开的管道?另一种说法是,我想在便携式外壳中做popen("some-program", "w")在C.中做的事情

具体来说,做这个片段所做的,不使用mkfifo:

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

注意:";bash";添加的标签主要是为了可见性。我理解使用";协处理器";bash、ksh或zsh中的扩展;我正在寻找一种在只有XPG Issue6(POSIX.1-2001(Shell和Utilities的设施可用的环境中工作的技术。此外,将不接受使用命名管道(最明显的是通过mkfifo(的应答,也不接受需要root权限的应答。

不鼓励使用以下任何一种答案,但我会听一个论点,即没有一种或多种答案是不可能做到的:

  • 创建机器语言可执行文件的任何内容(例如c99(
  • 任何主要用于交互的东西(例如vi(
  • 任何不属于";基本;第6版中的规范,或者在第6版或POSIX的任何后续版本中已过时(例如uucp(

我还将听取一个答案,该答案提出了一个令人信服的论点,即在上述限制范围内,这是不可能的。

(为什么没有命名管道?因为它们的行为与普通管道不太一样,尤其是在旧的、有缺陷的操作系统中,"只使用#!/bin/bash"不是一个选项。此外,在文件系统中公开管道,无论多么短暂,都意味着你不能完全排除一些无关进程打开管道的可能性。(

唯一提供用于创建管道的标准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

它本身是否必须是基于,并且您能容忍不同的父进程吗?

#! /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
...

使用这种不可靠的环境变量来检测脚本的第二次调用,可以更稳健地作为单独的脚本重新实现。警告行是您需要观察命令注入(CWE-78(的未来修订的地方。

过程的祖先看起来是这样的:

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

所有这些都在同一个进程组中,并且可能是从您的交互式shell(-bash?(派生而来。

以下是popen("some-program", "w")的一种方法,并使用POSIX shell通过fd3向其发送数据:

#!/bin/sh
{
{
# MAIN PROGRAM (basically, the whole script goes here)
echo "normal output that won't be processed by some-program"
echo 'some input for some-program' 1>&3
} 3>&1 1>&4 |
some-program
} 4>&1

关于问题的背景部分:使用上面的构造,some-program在一个子进程中运行;因此,除非您需要将some-program从其父组(即setpgid(0, 0)(分离,否则不需要在shell中使用任何&

最新更新