Чтение / запись одновременно Python-подпроцесса. Открыть
У меня есть простая программа на C, которая работает следующим образом: Запросить ввод Распечатать его Запрос другой ввод Распечатать еще раз
Теперь я использую python для вызова этой программы.
import subprocess
sobj = subprocess.Popen("./cprog", stdin = subprocess.PIPE, stdout = subprocess.PIPE)
sobj.stdin.write("2 3\n")
sobj.stdin.close()
sobj.stdout.read()
Это отлично работает. Точно так же с общение его работает нормально.
Но когда я пытаюсь сделать что-то подобное, это не сработает
sobj = subprocess.Popen("./cprog", stdin = subprocess.PIPE, stdout = subprocess.PIPE)
sobj.stdout.readline()
sobj.stdin.write("2 3\n")
sobj.stdin.close()
sobj.stdout.read()
Вот несколько вещей: 1. Я видел pexpect, но я думаю, что мы должны дать то, что программа просит заранее. 2. Могу ли я открыть закрытую трубу подпроцесса?
Я использую приведенный выше скрипт в качестве CGI, и я не знаю почему, но subprocess.call не будет работать в этом. Кто-нибудь может объяснить почему?
РЕДАКТИРОВАТЬ:
Я занимаюсь веб-проектом, где пользователи пишут код на C, C++ или JAVA и исполняют их в браузере. Итак, сначала я подумал об использовании PHP для него, но я не смог найти способ вызывать программы и запускать их в интерактивном режиме. Затем я увидел модуль подпроцесса python. Все работало нормально в интерпретаторе, когда я использовал subprocess.call. Но та же самая программа на Python, когда она была сохранена как.cgi и открыта в браузере, не работала. Тогда я начал смотреть на subprocess.popen. Но с этим мне нужно дать все входные данные в начале, а затем запустить код. Что я хочу сделать, это запустить интерактивный сеанс в браузере.
РЕДАКТИРОВАТЬ 2: Итак, я хочу, чтобы пользователь запускал программу в браузере и вводил ввод в текстовое поле, когда это необходимо, и этот ввод перенаправлялся в стандартный поток подпроцесса и выводился на его основе.
РЕДАКТИРОВАТЬ 3: cprog.c
#include <stdio.h>
int main() {
int x;
printf("Enter value of x: \n");
scanf("%d", &x);
printf("Value of x: %d\n", x);
return 0;
}
2 ответа
Когда я запускаю программу C напрямую через терминал, она работает нормально. Но когда я запускаю ту же программу со вторым кодом, я предоставил выше ничего не печатает.
Причина, по которой вы не видите никакого вывода, заключается в том, что C stdio использует буферизацию блоков, когда программа запускается в неинтерактивном режиме. Смотрите мой ответ, который демонстрирует несколько решений: pty
, stdbuf
, pexpect
, Если вы можете изменить код C, то вы также можете явно очистить вывод или сделать его небуферизованным.
Если вы можете предоставить все входные данные одновременно, а выходные данные ограничены, то вы можете использовать .communicate()
:
from subprocess import Popen, PIPE
p = Popen(["./cprog"], stdin=PIPE, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
out, err = p.communicate("2\n")
Итак, я хочу, чтобы пользователь запускал программу в браузере и вводил ввод в текстовое поле, когда это необходимо, и этот ввод перенаправлялся в стандартный поток подпроцесса и выводился на его основе.
Основано на примере ws-cli:
#!/usr/bin/python
"""WebSocket CLI interface.
Install: pip install twisted txws
Run: twistd -ny wscli.py
Visit http://localhost:8080/
"""
import sys
from twisted.application import strports # pip install twisted
from twisted.application import service
from twisted.internet import protocol
from twisted.python import log
from twisted.web.resource import Resource
from twisted.web.server import Site
from twisted.web.static import File
from txws import WebSocketFactory # pip install txws
class Protocol(protocol.Protocol):
def connectionMade(self):
from twisted.internet import reactor
log.msg("launch a new process on each new connection")
self.pp = ProcessProtocol()
self.pp.factory = self
reactor.spawnProcess(self.pp, command, command_args)
def dataReceived(self, data):
log.msg("redirect received data to process' stdin: %r" % data)
self.pp.transport.write(data)
def connectionLost(self, reason):
self.pp.transport.loseConnection()
def _send(self, data):
self.transport.write(data) # send back
class ProcessProtocol(protocol.ProcessProtocol):
def connectionMade(self):
log.msg("connectionMade")
def outReceived(self, data):
log.msg("send stdout back %r" % data)
self._sendback(data)
def errReceived(self, data):
log.msg("send stderr back %r" % data)
self._sendback(data)
def processExited(self, reason):
log.msg("processExited")
self._sendback('program exited')
def processEnded(self, reason):
log.msg("processEnded")
def _sendback(self, data):
self.factory._send(data)
command = './cprog'
command_args = [command]
application = service.Application("ws-cli")
echofactory = protocol.Factory()
echofactory.protocol = Protocol
strports.service("tcp:8076:interface=127.0.0.1",
WebSocketFactory(echofactory)).setServiceParent(application)
resource = Resource()
resource.putChild('', File('index.html'))
strports.service("tcp:8080:interface=127.0.0.1",
Site(resource)).setServiceParent(application)
где index.html
:
<!doctype html>
<title>Send input to subprocess using websocket and echo the response</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js">
</script>
<script>
// send keys to websocket and echo the response
$(document).ready(function() {
// create websocket
if (! ("WebSocket" in window)) WebSocket = MozWebSocket; // firefox
var socket = new WebSocket("ws://localhost:8076");
// open the socket
socket.onopen = function(event) {
// show server response
socket.onmessage = function(e) {
$("#output").text(e.data);
}
// sent input
$("#entry").keyup(function (e) {
socket.send($("#entry").attr("value")+"\n");
});
}
});
</script>
<pre id=output>Here you should see the output from the command</pre>
<input type=text id=entry value="123">
А также cprog.c
:
#include <stdio.h>
int main() {
int x = -1;
setbuf(stdout, NULL); // make stdout unbuffered
while (1) {
printf("Enter value of x: \n");
if (scanf("%d", &x) != 1)
return 1;
printf("Value of x: %d\n", x);
}
return 0;
}
Я предполагаю, что ваше приложение C отображает приглашение и ожидает, что пользователь введет свои данные в той же строке, и это в вашем readline()
позвоните выше, вы пытаетесь получить подсказку.
Если это так, readline()
будет блокироваться навсегда, потому что он ждет символа новой строки и никогда не увидит его. Если вы преобразуете этот вызов в простой read(X)
(где X
это число байтов, которое нужно прочитать за один раз), тогда вам, вероятно, повезет больше, хотя вам придется справляться с частичным вводом (т. е. циклически собирать ввод, пока не увидите всю подсказку). Единственная другая проблема, с которой вы можете столкнуться, это то, что приложение C не очищает вывод перед запросом пользователя, но я ожидаю, что вы увидите эту проблему и в интерактивном сеансе, если бы это было так.
При работе в контексте веб-сервера, такого как Apache, обычно плохая идея использовать такие вещи, как subprocess
поскольку они включают в себя разветвление дополнительных процессов, и это часто довольно сложная вещь для управления. Это связано с тем, что процесс fork дублирует большую часть состояния родительского процесса, и иногда это может вызвать проблемы. Я не говорю, что это не сработает, я просто говорю, что вы можете создать некоторые тонкие проблемы для себя, если вы не будете осторожны, и меня не удивит, если именно поэтому у вас возникли проблемы с использованием subprocess
,
Однако, чтобы дать более полезный совет, вам нужно точно описать ошибку, которую вы видите при вызове подпроцесса. Например, вполне вероятно, что будет выдано исключение, которое, вероятно, будет в журналах вашего веб-сервера - воспроизведение этого будет хорошим началом.