Чтение/запись одновременно python subprocess.Popen

У меня есть простая программа на 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;
 }

person Murali    schedule 12.01.2013    source источник
comment
Просто сказать, что это не сработает, никогда не поможет; вы должны рассказать нам, что вы хотели, чтобы произошло, и что на самом деле произошло. (Оба раза.) Кроме того, кажется, что это где-то между 3 и 6 отдельными вопросами, и неясно, что задает большинство из них, поэтому я не уверен, как вы ожидаете, что ответ будет выглядеть.   -  person abarnert    schedule 12.01.2013
comment
Один четкий вопрос с четким ответом: могу ли я снова открыть закрытый канал подпроцесса? Нет. В Python, а также в стандартах C или POSIX, на которых он основан, API повторного открытия для файлов и файловых объектов отсутствует. Если вы закроете файл, вы больше не сможете его использовать. Так что просто не закрывайте его, пока не закончите с ним. (Если вы закрыли его из-за того, что могли выйти из функции сразу после этого, но проблема в том, что вы могли не выйти, правильное решение — использовать with closing(f) или try/finally в редкие случаи, когда это неуместно.)   -  person abarnert    schedule 12.01.2013
comment
Вот основа для ответа на Я хочу запустить интерактивный сеанс в браузере. вопрос. client.py просто преобразует входной текст в верхний регистр, а не интерпретирует его с помощью какого-либо языка программирования.   -  person jfs    schedule 12.01.2013
comment
@ J.F.Sebastian Спасибо за ссылку. Пробовал данный код, но код сервера не ожидает ввода. Он просто печатает вывод, не дожидаясь ввода от пользователя.   -  person Murali    schedule 12.01.2013
comment
Вам действительно нужно более точно указать, что вы ожидаете и что действительно происходит во втором фрагменте кода (я думаю, что это должно работать для моего определения работы). Возможно, вам не хватает некоторых stdout.flush().   -  person Armin Rigo    schedule 12.01.2013
comment
Отредактировал мой пост с кодом, который я хочу выполнить. Когда я запускаю программу C напрямую через терминал, она работает нормально. Но когда я запускаю ту же программу со вторым кодом, который я указал выше, ничего не печатается.   -  person Murali    schedule 12.01.2013


Ответы (2)


Я предполагаю, что ваше приложение C отображает приглашение и ожидает, что пользователь введет свой ввод в той же строке, и что в вашем вызове readline() выше вы пытаетесь получить приглашение.

Если это так, readline() будет блокироваться навсегда, потому что он ждет символа новой строки и никогда его не видит. Если вы преобразуете этот вызов в простой read(X) (где X — это количество байтов, которые нужно прочитать за один раз), то вам, вероятно, повезет больше, хотя вы должны справиться с частичным вводом (т. е. циклически собирать ввод, пока не увидите всю подсказку). Единственная другая проблема, которую вы можете увидеть, это если приложение C не сбрасывает вывод перед запросом пользователя, но я ожидаю, что вы увидите эту проблему и в интерактивном сеансе, если это так.

При работе в контексте веб-сервера, такого как Apache, обычно не рекомендуется использовать такие вещи, как subprocess, поскольку они включают в себя дополнительные процессы, а это часто довольно сложная вещь для управления. Это связано с тем, что процесс ветвления дублирует большую часть состояния родителя, и иногда это может вызывать проблемы. Я не говорю, что это не сработает, я просто говорю, что вы можете создать для себя некоторые тонкие проблемы, если не будете осторожны, и меня не удивит, если именно поэтому у вас возникают проблемы с использованием subprocess.

Однако, чтобы дать какой-либо более полезный совет, вам нужно точно описать ошибку, которую вы видите при вызове подпроцесса. Например, вполне вероятно, что будет выброшено исключение, которое, вероятно, будет в журналах вашего веб-сервера — воспроизведение этого здесь было бы хорошим началом.

person Cartroo    schedule 12.01.2013
comment
Я не добавлял \n раньше. Но теперь даже после того, как сделал свою ту же проблему. Один раз проверьте 2-й и 3-й фрагмент кода. Я использую вторую программу python в качестве сценария cgi и запускаю ее в браузере. Теперь всякий раз, когда я запускаю этот скрипт, он продолжает загружаться. Тот же скрипт Python, когда я запускаю терминал, ничего не отображается. Я думаю, это входит в бесконечный цикл. - person Murali; 12.01.2013

Когда я запускаю программу 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;
}
person jfs    schedule 15.01.2013