Запись в стандартный процесс

10

Насколько я понимаю, если я наберу следующее ...

 python -i

... интерпретатор python теперь будет читать из stdin, ведя себя (очевидно) так:

 >>> print "Hello"
 Hello

Я ожидал бы, что это сделает то же самое, если я сделаю это:

 echo 'print "Hello"' > /proc/$(pidof python)/fd/0

Но это вывод (фактически пустая строка):

 >>> print "Hello"
 <empyline>

Это мне кажется, он просто взял print "Hello"\nи написал это stdout, но не интерпретировал это. Почему это не работает и что я должен сделать, чтобы это работало?

Sheppy
источник
TIOCSTI ioctl может записывать в стандартный ввод терминала, как если бы данные были введены с клавиатуры. Например, github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Ответы:

9

Отправка входных данных оболочкам / интерпретаторам таким образом очень проблематична и очень трудно заставить работать любым надежным способом.

Правильным способом является использование сокетов, поэтому они были изобретены, вы можете сделать это в командной строке, используя ncat ncили socatсвязать процесс Python с простым сокетом. Или напишите простое приложение на python, которое связывается с портом и прослушивает команды для интерпретации в сокете.

сокеты могут быть локальными и не подвергаться никакому веб-интерфейсу.


Проблема в том, что если вы запускаете pythonиз командной строки, он обычно подключен к вашей оболочке, которая подключена к терминалу, на самом деле мы можем видеть

$ ls -al /proc/PID/fd
lrwxrwxrwx 1 USER GROUP 0 Aug 1 00:00 0 -> /dev/pty1

поэтому, когда вы пишете в stdinpython, вы фактически пишете в ptypsuedo-терминал, который является устройством ядра, а не простым файлом. Он ioctlне использует readи write, поэтому вы увидите вывод на вашем экране, но он не будет отправлен в порожденный процесс ( python)

Один из способов повторить то, что вы пытаетесь, - с помощью fifoили named pipe.

# make pipe
$ mkfifo python_i.pipe
# start python interactive with pipe input
# Will print to pty output unless redirected
$ python -i < python_i.pipe &
# keep pipe open 
$ sleep infinity > python_i.pipe &
# interact with the interpreter
$ echo "print \"hello\"" >> python_i.pipe

Вы также можете использовать screenтолько для ввода

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
crasic
источник
Если вы держите трубу открытой (например sleep 300 > python_i.pipe &), другая сторона не закроется и pythonбудет продолжать принимать команды вниз по трубе. EOF как таковое не отправлено echo.
Ройма
@roaima Вы правы, я ошибался в моем понимании, что echo отправляет EOF, когда он закрывает поток. Этого нельзя избежать с помощью |труб, однако, верно?
авария
Я уже был на дороге fifo, но echo something > fifoзаставил его получить EOF, который остановит многие приложения. sleep infinity > fifoОбходной путь не пересек мою середину , хотя, спасибо!
Шеппи
1
фактически продолжая свою идею, вы также можете сделать то, python -i <> fifoчто также предотвратит EOF
Шеппи
10

Доступ не обращается к дескриптору 0 файла PID процесса , он обращается к файлу, который PID открыл в дескрипторе файла 0. Это тонкое различие, но оно имеет значение. Файловый дескриптор - это соединение, которое процесс имеет с файлом. Запись в файловый дескриптор пишет в файл независимо от того, как файл был открыт./proc/PID/fd/0

Если это обычный файл, запись в него изменяет файл. Данные не обязательно должны читаться следующим процессом: они зависят от позиции, прикрепленной к дескриптору файла, который процесс использует для чтения файла. Когда процесс открывается , он получает тот же файл, что и другой процесс, но позиции файла независимы./proc/PID/fd/0/proc/PID/fd/0

Если это канал, то запись в него добавляет данные в буфер канала. В этом случае процесс, который читает из канала, будет читать данные./proc/PID/fd/0

Если это терминал, то запись в него выводит данные на терминал. Файл терминала является двунаправленным: запись в него выводит данные, т.е. терминал отображает текст; считывание с терминала вводит данные, т.е. терминал передает пользовательский ввод./proc/PID/fd/0

Python читает и пишет в терминал. Когда вы бежите echo 'print "Hello"' > /proc/$(pidof python)/fd/0, вы пишете print "Hello"в терминал. Терминал отображается print "Hello"в соответствии с инструкциями. Процесс Python ничего не видит, он все еще ждет ввода.

Если вы хотите передать входные данные процессу Python, вам нужно получить терминал для этого. См . Ответ Крэстика, чтобы узнать, как это сделать.

Жиль "ТАК - прекрати быть злым"
источник
2

Основываясь на том, что сказал Жиль , если мы хотим записать в stdin процесса, который подключен к терминалу, нам фактически нужно отправить информацию в терминал. Тем не менее, поскольку терминал служит как формой ввода, так и вывода, при записи в него терминал не может знать, что вы хотите записать в выполняющийся в нем процесс, а не на «экран».

Однако в Linux есть не-posix способ имитации пользовательского ввода через запрос ioctl, называемый TIOCSTI(Terminal I / O Control - Simulate Terminal Input), который позволяет нам отправлять символы на терминал, как если бы они были набраны пользователем.

Я только поверхностно осознаю, как это работает, но исходя из этого ответа, должно быть возможно сделать это с чем-то вроде

import fcntl, sys, termios

tty_path = sys.argv[1]

with open(tty_path, 'wb') as tty_fd:
    for line in sys.stdin.buffer:
        for byte in line:
            fcntl.ioctl(tty_fd, termios.TIOCSTI, bytes([byte]))

Некоторые внешние ресурсы:

http://man7.org/linux/man-pages/man2/ioctl.2.html

http://man7.org/linux/man-pages/man2/ioctl_tty.2.html

Кристиан Реал-Флухарти
источник
Обратите внимание, что этот вопрос не относится к какой-либо конкретной операционной системе, и что TIOCSTI не возникла в Linux. Почти за два года до написания этого ответа люди начали отказываться от TIOCSTI по ​​соображениям безопасности. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP Отсюда и моё указание «Linux» (хотя я не уверен, откуда он возник). А под "людьми", кажется, вы имеете в виду некоторые BSD? Из того, что я прочитал, когда писал это, кажется, что в гораздо более старой реализации была угроза безопасности, которая с тех пор была исправлена, но BSD все же считал, что «безопаснее» вообще отказаться от ioctl. Я, однако, очень незнаком со всем этим, поэтому я решил, что лучше не говорить, что что-то было невозможно в определенных системах, когда у меня нет опыта работы с этой системой.
Кристиан Реал-Флухарти