Python `tee` стандартный дочерний процесс - PullRequest
9 голосов
/ 01 июля 2011

Есть ли в Python способ сделать эквивалент командной строки UNIX tee? Я делаю типичный шаблон fork / exec, и я хотел бы, чтобы стандартный вывод дочернего элемента появлялся одновременно как в файле журнала, так и на стандартном выводе родительского элемента без необходимости буферизации

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

pid = os.fork()
logFile = open(path,"w")
if pid == 0:
  os.dup2(logFile.fileno(),1)  
  os.execv(cmd)

edit : Я не хочу использовать модуль подпроцесса. Я делаю некоторые сложные вещи с дочерним процессом, который требует от меня вызова fork вручную.

Ответы [ 5 ]

6 голосов
/ 15 июля 2011

Здесь у вас есть рабочее решение без использования модуля subprocess.Хотя вы можете использовать его для процесса тройника, все еще используя набор функций exec* для своего пользовательского подпроцесса (просто используйте stdin=subprocess.PIPE, а затем скопируйте дескриптор в стандартный вывод).

import os, time, sys

pr, pw = os.pipe()
pid = os.fork()

if pid == 0:
    os.close(pw)
    os.dup2(pr, sys.stdin.fileno())
    os.close(pr)
    os.execv('/usr/bin/tee', ['tee', 'log.txt'])
else:
    os.close(pr)
    os.dup2(pw, sys.stdout.fileno())
    os.close(pw)

    pid2 = os.fork()

    if pid2 == 0:
        # Replace with your custom process call
        os.execv('/usr/bin/yes', ['yes'])
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            pass

Обратите внимание, чтовнутренне команда tee делает то же самое, что предложил Бен в своем ответе: чтение ввода и циклическое переключение дескрипторов выходных файлов при записи в них.Он может быть более эффективным из-за оптимизированной реализации и потому, что он написан на C, но у вас есть издержки различных каналов (не знаю точно, какое решение более эффективно, но, на мой взгляд, переназначение пользовательского файла, какобъект для stdout - более элегантное решение).

Еще несколько ресурсов:

6 голосов
/ 01 июля 2011

В дальнейшем SOMEPATH - это путь к дочернему исполняемому файлу в формате, подходящем для subprocess.Popen (см. Его документы).

import sys, subprocess


f = open('logfile.txt', 'w')
proc = subprocess.Popen(SOMEPATH, stdout=subprocess.PIPE)

while True:
    out = proc.stdout.read(1)
    if out == '' and proc.poll() != None:
        break
    if out != '':
        # CR workaround since chars are read one by one, and Windows interprets
        # both CR and LF as end of lines. Linux only has LF
        if out != '\r': f.write(out)
        sys.stdout.write(out)
        sys.stdout.flush()
2 голосов
/ 18 июля 2011

О, ты.У меня был приличный ответ до того, как я увидел последнюю строчку вашего примера: execv ().Ну, какашки.Первоначальная идея заключалась в замене стандартного стандартного вывода каждого дочернего процесса экземпляром тройного класса этого блога и разделением потока на исходный стандартный вывод и файл журнала:

http://www.shallowsky.com/blog/programming/python-tee.html

Но, поскольку вы используете execv (), экземпляр тройника дочернего процесса будет просто забит, так что это не сработает.

К сожалению, для вас не существует "готового" решения для вашегопроблема, которую я могу найти.Самым близким было бы создание самой программы в подпроцессе;если вы хотите быть более кроссплатформенным, вы можете создать простую замену Python.

Первое, что нужно знать при кодировании замены тройника: tee действительно простая программа.Во всех реальных реализациях C, которые я видел, это не намного сложнее, чем это:

while((character = read()) != EOF) {
    /* Write to all of the output streams in here, then write to stdout. */
}

К сожалению, вы не можете просто соединить два потока вместе.Это было бы очень полезно (чтобы вход одного потока автоматически переадресовывался из другого), но мы не можем позволить себе такую ​​роскошь, не кодируя его сами.Итак, у Эли и меня будут очень похожие ответы.Разница в том, что в моем ответе «тройник» Python будет выполняться в отдельном процессе через канал;таким образом, родительский поток все еще полезен!

(Помните: скопируйте класс тройки в блоге тоже.)

import os, sys

# Open it for writing in binary mode.
logFile=open("bar", "bw")

# Verbose names, but I wanted to get the point across.
# These are file descriptors, i.e. integers.
parentSideOfPipe, childSideOfPipe = os.pipe()

# 'Tee' subprocess.
pid = os.fork()
if pid == 0:
    while True:
        char = os.read(parentSideOfPipe, 1)
        logFile.write(char)
        os.write(1, char)

# Actual command
pid = os.fork()
if pid == 0:
    os.dup2(childSideOfPipe, 1)
    os.execv(cmd)

Извините, если вы этого не хотели, ноэто лучшее решение, которое я могу найти.

Удачи вам в остальной части вашего проекта!

2 голосов
/ 15 июля 2011

Будет ли такой подход делать то, что вы хотите?

import sys

class Log(object):
    def __init__(self, filename, mode, buffering):
        self.filename = filename
        self.mode = mode
        self.handle = open(filename, mode, buffering)

    def write(self, thing):
        self.handle.write(thing)
        sys.stdout.write(thing)

Вам, вероятно, потребуется реализовать больше интерфейса file, чтобы это было действительно полезно (и я не учел правильно)по умолчанию mode и buffering, если вы этого хотите).Затем вы можете сделать все свои записи в дочернем процессе в экземпляр журнала.Или, если вы хотите быть по-настоящему волшебным, и вы уверены , что реализовали достаточно интерфейса file, чтобы вещи не падали и не умирали, вы могли бы потенциально назначить sys.stdoutэкземпляр этого класса.Тогда я думаю , что любые средства записи в стандартный вывод, включая print, будут проходить через класс журнала.

Изменить, чтобы добавить: Очевидно, если вы назначите sys.stdout вам придется сделать что-то еще в методе write, чтобы отобразить вывод на стандартный вывод !!Я думаю, что вы могли бы использовать sys.__stdout__ для этого.

0 голосов
/ 19 июля 2011

Первый очевидный ответ - это раскошелиться на настоящий процесс тройника, но это, вероятно, не идеально.

Код тройника (из coreutils) просто читает каждую строку и записывает в каждый файл по очереди (эффективно буферизуя).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...