当您调用subprocess.Popen 时,您可以告诉它重定向stdout 和/或stderr。如果你不这样做,它会允许操作系统从 Python 进程的实际 STDOUT_FILENO 和 STDERR_FILENO(它们是固定常量,1 和 2)中复制,从而使它们不被重定向。
这意味着,如果 Python 的 fd 1 和 2 将进入您的 tty 会话(例如,可能在像 /dev/pts/0 这样的底层设备上),那么孩子——在这种情况下,孙子也是如此——直接交谈到同一个会话(同一个/dev/pts/0)。您在 Python 进程本身中所做的任何事情都无法改变这一点:它们是独立的进程,可以独立、直接地访问会话。
你可以做的是调用 ./child.sh 并重定向:
proc = subprocess.Popen('./child.sh', stdout=subprocess.PIPE)
快速旁注编辑:如果您想丢弃子及其孙子的 所有 输出,请打开 os.devnull(如您所做的那样,或使用 os.open() 获取原始整数文件描述符)并将 stdout 和 stderr 连接到底层文件描述符。如果您已将其作为 Python 流打开:
f = open(os.devnull, "w")
那么底层文件描述符是f.fileno():
proc = subprocess.Popen('./child.sh', stdout=f.fileno(), stderr=f.fileno())
在这种情况下,您无法从所涉及的任何进程中获得任何输出。
现在子文件描述符 1 连接到管道实体,而不是直接连接到会话。 (由于上面没有stderr=,所以child中的fd 2还是直接连接到session的。)
位于操作系统内部的管道实体只是从一端(管道的“写入端”)复制到另一端(“读取端”)。您的 Python 进程可以控制读取端。您必须在读取端调用 OS read 系统调用(通常不是直接调用,但请参见下文),以从中收集输出。
一般来说,如果您停止从读取端读取,管道会“填满”,并且任何尝试在写入端执行操作系统级别 write 的进程都会被“阻止”,直到有人可以访问读取端(又是你)从中读取。
如果您丢弃读取端,使管道无处转储其输出,则写入端开始返回 EPIPE 错误并发送 SIGPIPE 信号给任何尝试操作系统级别 write 调用的进程。当您调用操作系统级别的close 系统调用时会发生这种丢弃,假设您没有将描述符移交给其他进程。当您的进程退出时也会发生这种情况(再次在相同的假设下)。
没有方便的方法可以将读取端连接到像/dev/null 这样的无限数据接收器,至少在大多数类 Unix 系统中(有一些特殊的时髦系统调用可以做到这一点) “管道”)。但是,如果您打算杀死孩子并愿意让其孙辈死于SIGPIPE 信号,您可以简单地关闭描述符(或退出),让筹码落到可能的地方。
子孙可以通过将SIGPIPE设置为SIG_IGN或屏蔽SIGPIPE来保护自己免于死亡。信号掩码在exec 系统调用中继承,因此在某些情况下,您可以为子级屏蔽SIGPIPE(但有些子级会取消屏蔽信号)。
如果关闭描述符不合适,您可以创建一个简单地读取和丢弃传入管道数据的新进程。如果您使用fork 系统调用,这很简单。或者,一些类 Unix 系统允许您通过 AF_UNIX 套接字将文件描述符传递给其他不相关的(父/子)进程,因此您可以拥有一个执行此操作的守护进程,可通过 AF_UNIX 套接字访问。 (这对代码来说很重要。)
如果您希望子进程将其标准错误输出发送到 same 管道,以便您可以读取其标准输出和标准错误,只需将stderr=subprocess.STDOUT 添加到Popen() 调用。如果您希望子进程将其 stderr 输出发送到 单独 管道,请添加 stderr=subprocess.PIPE。但是,如果您执行后者,事情可能会变得有些棘手。
如上所述,为防止儿童阻塞,您必须调用 OS read 调用。如果只有一根管道,这很容易:
for line in proc.stdout:
...
例如,或者:
line = proc.stdout.readline()
将一次读取一行管道(Python 中的模缓冲)。您可以阅读任意多行或尽可能少的行。
但是,如果有 两个 管道,则必须读取“已满”的管道。 Python 的 subprocess 模块定义了 communicate() 函数来为您执行此操作:
stdout, stderr = proc.communicate()
这里的缺点是communicate() 读取到完成:它需要获得所有 输出,可以到达每个管道的写入端。这意味着它重复调用操作系统级别的read 操作,直到read 指示数据结束。仅当在某个时刻对相应管道的写端具有写访问权限的所有进程都关闭了该管道的该端时,才会发生这种情况。换句话说,它等待子和任何孙子close 连接到管道写入端的描述符。
一般来说,只使用一个管道要简单得多,尽可能多地读取(但只能尽可能多地),然后简单地关闭管道:
proc = subprocess.Popen('./child.sh', stdout=subprocess.PIPE)
line1 = proc.stdout.readline()
line2 = proc.stdout.readline()
# that's all we care about
proc.stdout.close()
proc.kill()
status = proc.wait()
这是否足够取决于您的特定问题。