Как закрыть подпроцесс Python 2.5.2 Popen, когда у меня есть данные, которые мне нужны? - PullRequest
9 голосов
/ 05 октября 2010

Я использую следующую версию Python:

$ /usr/bin/env python --version                                                                                                                                                            
Python 2.5.2                                    

Я запускаю следующий код Python для записи данных из дочернего подпроцесса в стандартный вывод и считываю их в переменную Python с именем metadata:

# Extract metadata (snippet from extractMetadata.py)
inFileAsGzip = "%s.gz" % inFile                                                                                                                                                                                                            
if os.path.exists(inFileAsGzip):                                                                                                                                                                                                           
    os.remove(inFileAsGzip)                                                                                                                                                                                                                
os.symlink(inFile, inFileAsGzip)                                                                                                                                                                                                           
extractMetadataCommand = "bgzip -c -d -b 0 -s %s %s" % (metadataRequiredFileSize, inFileAsGzip)                                                                                                                                            
metadataPipes = subprocess.Popen(extractMetadataCommand, stdin=None, stdout=subprocess.PIPE, shell=True, close_fds=True)                                                                                                      
metadata = metadataPipes.communicate()[0]                                                                                                                                                                                                                                                                                                                                                                                                          
metadataPipes.stdout.close()                                                                                                                                                                                                             
os.remove(inFileAsGzip) 
print metadata

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

$ extractMetadata.py | head

Ошибка появится, если я попаду в голову, awk, grep и т. д.

Скрипт заканчивается следующей ошибкой:

close failed: [Errno 32] Broken pipe

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

Ответы [ 4 ]

4 голосов
/ 05 октября 2010

Хммм.Я видел странность "Broken pipe" с помощью subprocess + gzip.Я так и не понял, почему это происходит, но изменив подход к реализации, я смог избежать этой проблемы.Похоже, вы просто пытаетесь использовать бэкэнд-процесс gzip для распаковки файла (вероятно, потому что встроенный модуль Python ужасно медленный ... не знаю почему, но это определенно так).* вместо этого вы можете рассматривать процесс как полностью асинхронный бэкэнд и просто читать его вывод по мере его поступления.Когда процесс умирает, модуль подпроцесса позаботится о том, чтобы убрать вещи для вас.Следующий фрагмент кода должен обеспечивать ту же базовую функциональность без проблем со сломанной трубой.

import subprocess

gz_proc = subprocess.Popen(['gzip', '-c', '-d', 'test.gz'], stdout=subprocess.PIPE)

l = list()
while True:
    dat = gz_proc.stdout.read(4096)
    if not d:
        break
    l.append(d)

file_data = ''.join(l)
1 голос
/ 10 октября 2010

Я думаю, что это исключение не имеет ничего общего ни с вызовом подпроцесса , ни с его файловыми дескрипторами (после вызова общаться объект popen закрывается).Кажется, это классическая проблема закрытия sys.stdout в трубе:

http://bugs.python.org/issue1596

Несмотря на то, что это ошибка 3-летней давности, она не была решена.Поскольку sys.stdout.write(...), похоже, тоже не помогает, вы можете прибегнуть к вызову более низкого уровня, попробуйте это:

os.write(sys.stdout.fileno(), metadata)
0 голосов
/ 09 октября 2010

Получение первых 10 строк из выходных данных процесса может работать лучше таким образом:

ph = os.popen(cmdline, 'r')
lines = []
for s in ph:
    lines.append(s.rstrip())
    if len(lines) == 10: break
print '\n'.join(lines)
ph.close()
0 голосов
/ 09 октября 2010

Недостаточно информации, чтобы ответить на этот вопрос окончательно, но я могу сделать некоторые обоснованные предположения.

Во-первых, os.remove определенно не должно быть неудачным с EPIPE.Это тоже не похоже;ошибка close failed: [Errno 32] Broken pipe, а не remove failed.Похоже, что close терпит неудачу, а не remove.

Возможно закрытие stdout канала, чтобы выдать эту ошибку.Если данные буферизуются, Python сбросит данные перед закрытием файла.Если базовый процесс исчез, выполнение этого вызовет IOError / EPIPE.Однако обратите внимание, что это не фатальная ошибка: даже когда это происходит, файл все еще закрыт .Следующий код воспроизводит это примерно в 50% случаев и демонстрирует, что файл закрывается после исключения.(Остерегайтесь; я думаю, что поведение bufsize изменилось в разных версиях.)

    import os, subprocess
    metadataPipes = subprocess.Popen("echo test", stdin=subprocess.PIPE,
        stdout=subprocess.PIPE, shell=True, close_fds=True, bufsize=4096)
    metadataPipes.stdin.write("blah"*1000)
    print metadataPipes.stdin
    try:
        metadataPipes.stdin.close()
    except IOError, e:
        print "stdin after failure: %s" % metadataPipes.stdin

Это неестественно;это происходит только часть времени.Это может объяснить, почему это выглядело как удаление или добавление вызова os.remove влияет на ошибку.

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

В качестве примечания, вы не должны проверять os.path.exists перед удалением файла.это может не существовать;это вызовет состояние гонки, если другой процесс одновременно удалит файл.Вместо этого сделайте следующее:

try:
    os.remove(inFileAsGzip)
except OSError, e:
    if e.errno != errno.ENOENT: raise

... которую я обычно заключаю в такую ​​функцию, как rm_f.

Наконец, если вы явно хотите уничтожить подпроцесс, есть metadataPipes.kill- просто закрытие его каналов не сделает этого - но это не поможет объяснить ошибку.Кроме того, опять же, если вы просто читаете файлы gzip, вам гораздо лучше использовать модуль gzip, чем подпроцесс.http://docs.python.org/library/gzip.html

...