Непоследовательные результаты, вызывающие sed на конвейере с использованием подпроцесса - PullRequest
1 голос
/ 12 июня 2019

Я перевожу bash-скрипт для выполнения вычислений и выполнения некоторой постобработки в Python и столкнулся с трудностями, когда попытался передать вывод программы в sed. Проблема заключается в переводе такого рода конвейера:

#!/bin/bash
echo -e "whatever\n1 2" | ./a.out | sed -e 's/.* //'

где исполняемый файл компилируется из:

#include <iostream>
#include <string>

#define FLUSH true

int main(int argc, char** argv) {
    std::string filename;
    int param1, param2;
    std::cout << "Input name of file: " << std::flush;
    std::cin >> filename;
    std::cout << "Enter params, separated by a space: " << std::flush;
    std::cin >> param1 >> param2;
    for(int i = 0; i < 400; i++) {
        std::cout << "Result " << i << ": " << i*param1+i*i*param2/(param1+i) << "\n";
        if(FLUSH) {
            std::cout << std::flush;
        }   
    }   
}

Я пробую следующее

import subprocess
compute = subprocess.Popen(['./a.out'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
strip = subprocess.Popen(['sed','-e', 's/.* //'], stdin=compute.stdout, stdout=subprocess.PIPE)
out, err = compute.communicate('filename.csv\n1 2')
print out

Но вместо получения списка необработанных выходных данных, как и ожидалось, я получаю случайную подпоследовательность строк из выходных данных программы, например:

Result 0: 0
Result 2: 4
Result 3: 7
Result 4: 10
Result 5: 13
Result 6: 16
Result 14: 40
...
Result 392: 1174
Result 396: 1186
Result 399: 1195

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

Вторая проблема заключается в том, почему сценарий sed не выполняет желаемого удаления подстроки в моем примере с игрушкой; он отлично работает в реальном сценарии, который я пишу, хотя случайные строки не передаются по всему конвейеру.

Для ясности, желаемый результат будет выглядеть так:

0
2
4
7
10
13
16
19
...
1183
1186
1189
1192
1195

1 Ответ

1 голос
/ 12 июня 2019

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

Так что communicate здесь не работает, что прискорбно, потому что запись вручную вcompute.stdin будет взаимоблокировка , если sed ожидает от вас слива его выходной трубы.(С таким небольшим вводом этого не произойдет, но в целом это небезопасно.) Вы могли бы ужасно обманывать, назначая один поток объекта Popen другому перед вызовом communicate;Другой простой подход - использовать другой поток для записи ввода.Тем не менее, доступны другие подходы, такие как select или обертки для него (или аналогичные мультиплексоры), такие как asyncio.

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

...