Menulis ke stdin dari suatu proses

10

Sejauh yang saya mengerti jika saya mengetik berikut ...

 python -i

... python-interpreter sekarang akan membaca dari stdin, berperilaku (jelas) seperti ini:

 >>> print "Hello"
 Hello

Saya berharap itu melakukan hal yang sama jika saya melakukan ini:

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

Tapi ini outputnya (dengan garis kosong yang sebenarnya):

 >>> print "Hello"
 <empyline>

Bagi saya ini terlihat seperti, hanya butuh print "Hello"\ndan menulisnya stdout, tetapi tidak menafsirkannya. Mengapa itu tidak berhasil dan apa yang harus saya lakukan untuk membuatnya bekerja?

Sheppy
sumber
TIOCSTI ioctl dapat menulis ke stdin terminal seolah-olah data telah dimasukkan dari keyboard. Misalnya github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Jawaban:

9

Mengirim masukan ke shell / juru bahasa dengan cara ini sangat rawan masalah dan sangat sulit untuk bekerja dengan cara yang dapat diandalkan.

Cara yang tepat adalah dengan menggunakan soket, ini sebabnya mereka diciptakan, Anda dapat melakukan ini di command line menggunakan ncat ncatau socatuntuk mengikat proses python ke soket sederhana. Atau tulis aplikasi python sederhana yang mengikat ke port dan mendengarkan perintah untuk menafsirkan pada soket.

soket bisa lokal dan tidak terpapar ke antarmuka web apa pun.


Masalahnya adalah bahwa jika Anda mulai pythondari baris perintah, biasanya dilampirkan ke shell Anda yang terpasang ke terminal, pada kenyataannya kita bisa melihat

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

jadi ketika Anda menulis ke stdinpython, Anda sebenarnya menulis ke ptyterminal psuedo, yang merupakan perangkat kernel, bukan file sederhana. Ini menggunakan ioctltidak readdan write, jadi Anda akan melihat output di layar Anda, tetapi tidak akan dikirim ke proses spawned ( python)

Salah satu cara untuk meniru apa yang Anda coba adalah dengan fifoatau 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

Anda juga dapat menggunakan screeninput saja

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
crasic
sumber
Jika Anda memegang pipa terbuka (misalnya sleep 300 > python_i.pipe &) sisi lain tidak akan menutup dan pythonakan terus menerima perintah di bawah pipa. Tidak ada EOF yang dikirim oleh echo.
roaima
@roaima Anda benar, saya salah dalam pemahaman saya bahwa gema mengirimkan EOF ketika menutup aliran. Namun, ini tidak bisa dihindari dengan |pipa, benar?
crasic
Saya sudah di ujung jalan, tetapi echo something > fifoakan menyebabkannya mendapatkan EOF yang akan menghentikan banyak aplikasi. The sleep infinity > fifosolusi tidak menyeberangi pertengahan saya meskipun, terima kasih!
Sheppy
1
sebenarnya melanjutkan ide Anda, Anda juga dapat melakukan python -i <> fifoyang juga akan mencegah EOF
Sheppy
10

Mengakses tidak mengakses file deskriptor 0 dari proses PID , itu mengakses file yang PID telah dibuka pada file deskriptor 0. Ini adalah perbedaan yang halus, tetapi itu penting. Deskriptor file adalah koneksi yang dimiliki suatu proses ke file. Menulis ke deskriptor file menulis ke file terlepas dari bagaimana file telah dibuka./proc/PID/fd/0

Jika ini file biasa, menulis padanya memodifikasi file. Data belum tentu proses apa yang akan dibaca selanjutnya: itu tergantung pada posisi terlampir pada deskriptor file yang digunakan proses untuk membaca file. Ketika suatu proses terbuka , itu mendapatkan file yang sama dengan proses lainnya, tetapi posisi file independen./proc/PID/fd/0/proc/PID/fd/0

Jika merupakan pipa, maka menulis ke dalamnya menambahkan data ke buffer pipa. Dalam hal ini, proses yang membaca dari pipa akan membaca data./proc/PID/fd/0

Jika adalah terminal, maka menulis kepadanya output data pada terminal. File terminal adalah dua arah: menulis kepadanya menghasilkan data, yaitu terminal menampilkan teks; membaca dari terminal input data, yaitu terminal mentransmisikan input pengguna./proc/PID/fd/0

Python membaca dan menulis ke terminal. Ketika Anda menjalankan echo 'print "Hello"' > /proc/$(pidof python)/fd/0, Anda sedang menulis print "Hello"ke terminal. Terminal ditampilkan print "Hello"sesuai instruksi. Proses python tidak melihat apa-apa, masih menunggu input.

Jika Anda ingin memasukkan input ke proses Python, Anda harus meminta terminal untuk melakukannya. Lihat jawaban crasic untuk cara melakukan itu.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
2

Membangun dari apa yang dikatakan Gilles , jika kita ingin menulis ke stdin proses yang melekat pada terminal, kita benar-benar perlu mengirim informasi ke terminal. Namun, karena terminal berfungsi sebagai bentuk input dan juga output, saat menulis ke terminal, tidak ada cara untuk mengetahui bahwa Anda ingin menulis ke proses yang berjalan di dalamnya daripada "layar".

Namun Linux memiliki cara non-posix untuk mensimulasikan input pengguna melalui permintaan ioctl yang disebut TIOCSTI(Terminal I / O Control - Simulasi Terminal Input) yang memungkinkan kita untuk mengirim karakter ke terminal seolah-olah mereka diketik oleh pengguna.

Saya hanya secara dangkal menyadari bagaimana ini bekerja, tetapi berdasarkan jawaban ini , harus mungkin untuk melakukan ini dengan sesuatu di sepanjang garis

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

Beberapa sumber daya eksternal:

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

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

Christian Reall-Fluharty
sumber
Perhatikan bahwa pertanyaannya tidak spesifik untuk sistem operasi tertentu, dan bahwa TIOCSTI tidak berasal dari Linux. Hampir dua tahun sebelum jawaban ini ditulis, orang-orang mulai meninggalkan TIOCSTI karena alasan keamanan. unix.stackexchange.com/q/406690/5132
JdeBP
@ JdeBP Oleh karena itu saya menentukan "Linux" (meskipun saya tidak yakin dari mana asalnya). Dan dengan "orang", sepertinya Anda maksud beberapa BSD? Dari apa yang saya baca ketika saya menulis ini, tampaknya ada, dalam implementasi yang jauh lebih tua, risiko keamanan yang sejak itu telah ditambal, tetapi BSD masih menganggapnya "lebih aman" untuk melepaskan ioctl sama sekali. Namun saya sangat tidak terbiasa dengan semua ini, jadi saya pikir lebih baik tidak mengatakan bahwa ada sesuatu yang tidak mungkin pada sistem tertentu ketika saya tidak memiliki pengalaman dengan sistem itu.
Christian Reall-Fluharty