Pisanie do standardowego procesu

10

O ile rozumiem, jeśli wpisuję następujące ...

 python -i

... interpreter Pythona będzie teraz czytał ze standardowego wejścia, zachowując się (oczywiście) w następujący sposób:

 >>> print "Hello"
 Hello

Spodziewałbym się, że zrobi to samo, jeśli to zrobię:

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

Ale to jest wynik (wypełnienie rzeczywistej pustej linii):

 >>> print "Hello"
 <empyline>

To mi wygląda, po prostu wziąłem print "Hello"\ni napisałem stdout, ale nie zinterpretowałem. Dlaczego to nie działa i co musiałbym zrobić, aby to działało?

Sheppy
źródło
Ioctl TIOCSTI może zapisywać na standardowe wejście terminala, tak jakby dane zostały wprowadzone z klawiatury. Na przykład github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Odpowiedzi:

9

Przesyłanie danych wejściowych do powłok / tłumaczy w ten sposób jest bardzo podatne na problemy i bardzo trudno jest uzyskać niezawodne działanie.

Właściwym sposobem jest użycie gniazd, dlatego zostały one wynalezione, możesz to zrobić w wierszu poleceń, używając ncat nclub w socatcelu powiązania procesu Pythona z prostym gniazdem. Lub napisz prostą aplikację pythonową, która łączy się z portem i nasłuchuje poleceń interpretowanych na gnieździe.

gniazda mogą być lokalne i nie mogą być narażone na żaden interfejs WWW.


Problem polega na tym, że jeśli zaczynasz pythonz wiersza poleceń, zwykle jest on dołączony do powłoki, która jest podłączona do terminala, w rzeczywistości możemy zobaczyć

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

więc kiedy piszesz do stdinPythona, w rzeczywistości piszesz na ptyterminalu psuedo, który jest urządzeniem jądra, a nie prostym plikiem. Używa ioctlnie readi write, więc zobaczysz wynik na ekranie, ale nie zostanie wysłany do odrodzonego procesu ( python)

Jednym ze sposobów replikacji tego, co próbujesz, jest za pomocą fifolub 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

Możesz także użyć screentylko do wprowadzania danych

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
poważny
źródło
Jeśli trzymasz rurkę otwartą (np. sleep 300 > python_i.pipe &), Druga strona się nie zamknie i pythonbędzie nadal akceptować polecenia w rurce. EOF nie jest wysyłany jako taki echo.
roaima,
@roaima masz rację, pomyliłem się, rozumiejąc, że echo wysyła EOF, gdy zamyka strumień. Nie da się tego uniknąć w przypadku |rur, prawda?
crasic
Byłem już na drodze piątej, ale echo something > fifospowodowałoby to uzyskanie EOF, który zatrzymałby wiele aplikacji. To sleep infinity > fifoobejście nie przeszło mi przez środek, dziękuję!
Sheppy,
1
kontynuując swój pomysł, możesz również zrobić, python -i <> fifoco również zapobiegnie EOF
Sheppy
10

Dostęp nie ma dostępu do deskryptora pliku 0 PID procesu , uzyskuje dostęp do pliku, który PID otworzył na deskryptorze pliku 0. Jest to subtelne rozróżnienie, ale ma znaczenie. Deskryptor pliku to połączenie procesu z plikiem. Zapis do deskryptora pliku zapisuje do pliku niezależnie od tego, jak plik został otwarty./proc/PID/fd/0

Jeśli jest to zwykły plik, zapis do niego modyfikuje plik. Dane niekoniecznie będą dalej czytane przez proces: zależy to od pozycji dołączonej do deskryptora pliku, którego proces używa do odczytu pliku. Po otwarciu procesu otrzymuje ten sam plik co drugi proces, ale pozycje plików są niezależne./proc/PID/fd/0/proc/PID/fd/0

Jeśli jest potokiem, wówczas zapis do niego dołącza dane do bufora potoku. W takim przypadku proces odczytujący z potoku odczyta dane./proc/PID/fd/0

Jeśli jest to terminal, wówczas zapis do niego wyprowadza dane na terminal. Plik terminala jest dwukierunkowy: zapis do niego wyprowadza dane, tzn. Terminal wyświetla tekst; odczyt z terminala wprowadza dane, tj. terminal przesyła dane użytkownika./proc/PID/fd/0

Python zarówno odczytuje, jak i pisze do terminala. Kiedy biegniesz echo 'print "Hello"' > /proc/$(pidof python)/fd/0, piszesz print "Hello"do terminala. Terminal wyświetla się print "Hello"zgodnie z instrukcją. Proces python nic nie widzi, wciąż czeka na dane wejściowe.

Jeśli chcesz wprowadzić dane wejściowe do procesu Python, musisz uzyskać terminal, który to zrobi. Zobacz odpowiedź crasic, aby dowiedzieć się, jak to zrobić.

Gilles „SO- przestań być zły”
źródło
2

Opierając się na tym, co powiedział Gilles , jeśli chcemy napisać do standardowego wejścia procesu podłączonego do terminala, faktycznie musimy wysłać informacje do terminala. Ponieważ jednak terminal służy zarówno jako forma wejścia, jak i wyjścia, podczas pisania do niego terminal nie ma możliwości dowiedzenia się, że chcesz pisać do uruchomionego procesu, a nie „ekranu”.

Linux ma jednak nie posiksowy sposób symulowania danych wprowadzanych przez użytkownika za pomocą żądania ioctl o nazwie TIOCSTI(Terminal I / O Control - Simulate Terminal Input), który pozwala nam wysyłać znaki do terminala tak, jakby zostały wpisane przez użytkownika.

Jestem tylko pozornie świadomy tego, jak to działa, ale na podstawie tej odpowiedzi powinno być możliwe zrobienie tego za pomocą czegoś podobnego do

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]))

Niektóre zasoby zewnętrzne:

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

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

Christian Reall-Fluharty
źródło
Zauważ, że pytanie nie dotyczy konkretnego systemu operacyjnego i że TIOCSTI nie pochodzi z Linuksa. Prawie dwa lata przed napisaniem tej odpowiedzi ludzie zaczęli porzucać TIOCSTI ze względów bezpieczeństwa. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP Stąd moje określenie „Linux” (chociaż nie jestem pewien, skąd się wziął). A przez „ludzi” wydaje się, że masz na myśli niektóre BSD? Z tego, co przeczytałem, kiedy to napisałem, wydaje się, że w znacznie starszej implementacji istniało ryzyko bezpieczeństwa, które zostało załatane, ale BSD nadal uważało, że „bezpieczniej” całkowicie upuścić ioctl. Nie znam się jednak na żadnym z tych zagadnień, więc pomyślałem, że lepiej nie mówić, że w niektórych systemach coś nie jest możliwe, gdy nie mam doświadczenia z tym systemem.
Christian Reall-Fluharty