Использовать StringIO как stdin с Popen

У меня есть следующая оболочка script, которую я бы хотел написать в Python (конечно, grep . на самом деле гораздо более сложная команда):

#!/bin/bash

(cat somefile 2>/dev/null || (echo 'somefile not found'; cat logfile)) \
| grep .

Я пробовал это (у которого в любом случае нет эквивалента cat logfile):

#!/usr/bin/env python

import StringIO
import subprocess

try:
    myfile = open('somefile')
except:
    myfile = StringIO.StringIO('somefile not found')

subprocess.call(['grep', '.'], stdin = myfile)

Но я получаю сообщение об ошибке AttributeError: StringIO instance has no attribute 'fileno'.

Я знаю, что вместо StringIO следует использовать subprocess.communicate(), чтобы отправлять строки в процесс grep, но я не знаю, как смешивать обе строки и файлы.

Ответ 1

p = subprocess.Popen(['grep', '...'], stdin=subprocess.PIPE, 
                                      stdout=subprocess.PIPE)
output, output_err = p.communicate(myfile.read())

Ответ 2

Не используйте голый except, он может перехватить слишком много. В Python 3:

#!/usr/bin/env python3
from subprocess import check_output

try:
    file = open('somefile', 'rb', 0)
except FileNotFoundError:
    output = check_output(cmd, input=b'somefile not found')
else:
    with file:
        output = check_output(cmd, stdin=file)

Он работает для больших файлов (файл перенаправляется на уровне дескриптора файла - нет необходимости загружать его в память).

Если у вас есть файловый объект (без реального .fileno()); вы можете напрямую писать в трубу с помощью метода .write():

#!/usr/bin/env python3
import io
from shutil import copyfileobj
from subprocess import Popen, PIPE
from threading import Thread

try:
    file = open('somefile', 'rb', 0)
except FileNotFoundError:
    file = io.BytesIO(b'somefile not found')

def write_input(source, sink):
    with source, sink:
        copyfileobj(source, sink)

cmd = ['grep', 'o']
with Popen(cmd, stdin=PIPE, stdout=PIPE) as process:
    Thread(target=write_input, args=(file, process.stdin), daemon=True).start()
    output = process.stdout.read()