Как я могу использовать Python для передачи stdin / stdout в Perl-скрипт - PullRequest
3 голосов
/ 02 января 2012

Этот код Python хорошо передает данные через скрипт Perl.

import subprocess
kw = {}
kw['executable'] = None
kw['shell'] = True
kw['stdin'] = None
kw['stdout'] = subprocess.PIPE
kw['stderr'] = subprocess.PIPE
args = ' '.join(['/usr/bin/perl','-w','/path/script.perl','<','/path/mydata'])
subproc = subprocess.Popen(args,**kw)
for line in iter(subproc.stdout.readline, ''):
    print line.rstrip().decode('UTF-8')

Однако сначала мне нужно сохранить свои буферы в файл на диске (/ path / mydata).Проще чередовать данные в коде Python и построчно передавать подпроцессу так:

import subprocess
kw = {}
kw['executable'] = '/usr/bin/perl'
kw['shell'] = False
kw['stderr'] = subprocess.PIPE
kw['stdin'] = subprocess.PIPE
kw['stdout'] = subprocess.PIPE
args = ['-w','/path/script.perl',]
subproc = subprocess.Popen(args,**kw)
f = codecs.open('/path/mydata','r','UTF-8')
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    print line.strip()  ### code hangs after printing this ###
    for line in iter(subproc.stdout.readline, ''):
        print line.rstrip().decode('UTF-8')
subproc.terminate()
f.close()

Код зависает с readline после отправки первой строки в подпроцесс.У меня есть другие исполняемые файлы, которые точно используют этот же код.

Мои файлы данных могут быть довольно большими (1,5 ГБ). Есть ли способ выполнить передачу данных без сохранения в файл?Я не хочу переписывать скрипт на Perl для совместимости с другими системами.

Ответы [ 3 ]

1 голос
/ 02 января 2012

Ваш код блокируется в строке:

for line in iter(subproc.stdout.readline, ''):

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

Кроме того, вы столкнулись с проблемами с буферизацией, как уже указывал Крис Морган. В другом вопросе о стековом потоке обсуждается, как можно выполнять неблокирующие операции чтения с подпроцессом. Я взломал быструю и грязную адаптацию кода из этого вопроса к вашей проблеме:

def enqueue_output(out, queue):
    for line in iter(out.readline, ''):
        queue.put(line)
    out.close()

kw = {}
kw['executable'] = '/usr/bin/perl'
kw['shell'] = False
kw['stderr'] = subprocess.PIPE
kw['stdin'] = subprocess.PIPE
kw['stdout'] = subprocess.PIPE
args = ['-w','/path/script.perl',]
subproc = subprocess.Popen(args, **kw)
f = codecs.open('/path/mydata','r','UTF-8')
q = Queue.Queue()
t = threading.Thread(target = enqueue_output, args = (subproc.stdout, q))
t.daemon = True
t.start()
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    print "Sent:", line.strip()  ### code hangs after printing this ###
    try:
        line = q.get_nowait()
    except Queue.Empty:
        pass
    else:
        print "Received:", line.rstrip().decode('UTF-8')

subproc.terminate()
f.close()

Вполне вероятно, что вам нужно будет внести изменения в этот код, но по крайней мере он не блокируется.

1 голос
/ 02 января 2012

Спасибо srgerg.Я также попробовал решение потоков.Однако одно это решение всегда висело.И в моем предыдущем коде, и в коде srgerg отсутствовало окончательное решение. Ваш совет дал мне одну последнюю идею.

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

def std_output(stdout,outfile=''):
    out = 0
    f = codecs.open(outfile,'w','UTF-8')
    for line in iter(stdout.readline, ''):
        f.write('%s\n'%(line.rstrip().decode('UTF-8')))
        out += 1
        if i == out: break
    stdout.close()
    f.close()

outfile = '/path/myout'
infile = '/path/mydata'

subproc = subprocess.Popen(args,**kw)
t = threading.Thread(target=std_output,args=[subproc.stdout,outfile])
t.daemon = True
t.start()

i = 0
f = codecs.open(infile,'r','UTF-8')
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    i += 1
subproc.stdin.write('%s\n'%(' '*4096)) ### push dummy data ###
f.close()
t.join()
subproc.terminate()
0 голосов
/ 02 января 2012

См. Предупреждения, упомянутые в руководстве по использованию Popen.stdin и Popen.stdout (чуть выше Popen.stdin):

Предупреждение: Используйте communicate() вместо .stdin.write, .stdout.read или .stderr.read чтобы избежать взаимных блокировок из-за того, что другие буферы конвейера ОС заполняют и блокируют дочерний процесс.

Я понимаю, что наличие в памяти строки размером полтора гигабайта не очень желательно, но использование communicate() - это способ, которым будет работать в то время как, как вы заметили, когда буфер канала ОС заполняется, путь stdin.write() + stdout.read() может оказаться заблокированным.

Возможно ли использование communicate() для вас?

...