python pty.fork - как это работает - PullRequest
8 голосов
/ 26 октября 2010

http://docs.python.org/library/pty.html говорит -

pty.fork () ¶ Вилка. Подключите управляющий терминал ребенка к псевдо-терминалу. Возвращаемое значение (pid, fd). Обратите внимание, что дочерний элемент получает pid 0, а fd недействителен. Возвращаемое значение родителя - это pid дочернего элемента, а fd - дескриптор файла, подключенный к управляющему терминалу дочернего элемента (а также к стандартному входу и выходу дочернего элемента).

Что это значит? Каждый процесс имеет 3 fd (stdin, stdout, stderr). Влияет ли это на эти fds сейчас? у дочернего процесса не будет ни одного из этих fds? Я в замешательстве .-- полностью.

Ответы [ 5 ]

13 голосов
/ 05 августа 2011

Я думаю, что наконец-то получил минимальный пример для pty.fork в Python - и так как мне было очень трудно найти подобный пример, я публикую его здесь в качестве иллюстрации ответа @ joni.Он основан на:

Особенно неприятные биты находят документацию, которая все еще ссылается на master_open(), который устарел;и тот факт, что pty.fork будет не порождать дочерний процесс, , если дескриптор файла (возвращаемый методом fork) не будет считан родительским процессом!( обратите внимание, что в os.fork такого требования нет ) Кроме того, кажется, что os.fork немного более переносим (прочитайте несколько комментариев, отметив, что pty.fork не работает на некоторых платформах),

В любом случае, вот первый скрипт (pyecho.py), который действует как исполняемый файл (он просто читает строки из стандартного ввода и записывает их обратно в верхнем регистре):

#!/usr/bin/env python
# pyecho.py

import sys;

print "pyecho starting..."

while True:
  print sys.stdin.readline().upper()

..... а затем вот фактический скрипт (для этого потребуется, чтобы файл pyecho.py находился в том же каталоге):

#!/usr/bin/env python

import sys
import os
import time
import pty

def my_pty_fork():

  # fork this script
  try:
    ( child_pid, fd ) = pty.fork()    # OK
    #~ child_pid, fd = os.forkpty()      # OK
  except OSError as e:
    print str(e)

  #~ print "%d - %d" % (fd, child_pid)
  # NOTE - unlike OS fork; in pty fork we MUST use the fd variable
  #   somewhere (i.e. in parent process; it does not exist for child)
  # ... actually, we must READ from fd in parent process...
  #   if we don't - child process will never be spawned!

  if child_pid == 0:
    print "In Child Process: PID# %s" % os.getpid()
    # note: fd for child is invalid (-1) for pty fork!
    #~ print "%d - %d" % (fd, child_pid)

    # the os.exec replaces the child process
    sys.stdout.flush()
    try:
      #Note: "the first of these arguments is passed to the new program as its own name"
      # so:: "python": actual executable; "ThePythonProgram": name of executable in process list (`ps axf`); "pyecho.py": first argument to executable..
      os.execlp("python","ThePythonProgram","pyecho.py")
    except:
      print "Cannot spawn execlp..."
  else:
    print "In Parent Process: PID# %s" % os.getpid()
    # MUST read from fd; else no spawn of child!
    print os.read(fd, 100) # in fact, this line prints out the "In Child Process..." sentence above!

    os.write(fd,"message one\n")
    print os.read(fd, 100)        # message one
    time.sleep(2)
    os.write(fd,"message two\n")
    print os.read(fd, 10000)      # pyecho starting...\n MESSAGE ONE
    time.sleep(2)
    print os.read(fd, 10000)      # message two \n MESSAGE TWO
    # uncomment to lock (can exit with Ctrl-C)
    #~ while True:
      #~ print os.read(fd, 10000)


if __name__ == "__main__":
    my_pty_fork()

Хорошо, надеюсь, это кому-нибудь поможет,
Cheers!

3 голосов
/ 29 октября 2013

Основной смысл использования pty.fork () заключается в том, что возвращаемый дескриптор псевдотерминального (pty) файла может использоваться для связи с порожденным процессом другим способом, т.е. через прямую запись и чтение с его (псевдо) терминала - вместо stdin / out / err.

Также есть дополнительная информация о pty и tty (из StackOverflow) и ссылка на простой пример использования pty.fork () .

2 голосов
/ 04 сентября 2018

При использовании pty.fork() дочернему процессу сообщается, что он пишет в реальный терминал, tty, точно так же, как тот, который вы обычно используете. Однако, он пишет в pty, псевдо-терминал, которыйэто tty, контролируемый другой программой.

Существует только один fd, потому что дочерняя программа записывает то, что будет в терминале.Это комбинация stdout, stderr и любых управляющих кодов терминала. stdout / stderr не имеют никакого смысла в этом контексте, так как они печатаются на терминале, и они не доступны индивидуально, когда программа подключена к pty (так же, как когда вы читаете вывод программы, вы не можете сказать,какой поток какой).

Вы все еще можете перенаправить stdout или stderr в файл, если хотите.Это будет сделано в раздвоенной части кода, выполняемой дочерним элементом.Вы можете перенаправить его стандартные потоки или перенаправить потоки подпроцесса.

Вот пример программы, основанной на ответе sdaau (их ответ не работает в Python3).

#!/usr/bin/env python3

import sys
import os
import time
import pty
import subprocess


def log(chars):
    sys.stdout.write("    > " + chars + "\n")


def main():

    # fork this script such that a child process writes to a pty that is
    # controlled or "spied on" by the parent process

    (child_pid, fd) = pty.fork()

    # A new child process has been spawned and is continuing from here.
    # The original parent process is also continuing from here.
    # They have "forked".

    if child_pid == 0:
        log("This is the child process fork, pid %s" % os.getpid())
        log("Child process will run a subprocess controlled by the parent process")
        log("All output, including this text, will be written to a pty and handled ")
        log("by the parent process.")
        # redirect stdout/stderr if you want to here
        subprocess.run(["bash"])

    else:
        log("This is the parent process fork, pid %s" % os.getpid())
        log("the fd being read from, %s, is not stdout nor stderr; it is " % fd)
        log("simply what the child is trying to write to its tty. ")
        log("stdout/stderr are combined along with terminal escape codes.")

        print()
        # Read initial output of child process before "typing" anything in its pty
        sys.stdout.write(os.read(fd, 1024).decode())
        print()

        # Run any bash commands you want. I/O to the fd is handled as if you are typing
        # at a terminal.
        os.write(fd, "ls\n".encode())
        os.write(fd, "which git\n".encode())
        # you can even test tab completions
        os.write(fd, "git sta\t\t".encode())
        while True:
            log("parent will read 1024 bytes that the child wrote to its pty")
            log("if no new output is available, parent will wait. Exit with ctrl+c.\n")
            # take out decode() to see raw bytes the child wrote to its pty
            sys.stdout.write(os.read(fd, 1024).decode())
            time.sleep(1)


if __name__ == "__main__":
    main()
2 голосов
/ 26 октября 2010

"и fd - файловый дескриптор, подключенный к дочернему управляющему терминалу" -> Дочерний процесс не увидит никакой разницы, он сможет нормально обращаться к stdin / out (я не знаю о stderr).Единственное отличие состоит в том, что на другой стороне «канала» находится не терминал, где пользователь читает / печатает, но родительский процесс, к которому можно получить доступ, возвращается с помощью fd.

0 голосов
/ 27 октября 2010

Спасибо, Джони. Вот то, что я понял. Когда вызывается pty.fork (). родительский процесс связан с мастером ptmx. родитель будет ждать ввода с клавиатуры или данных от мастера.

ребенок закрывает свои stdin, stdout и stderr. И дублирует рабов stdin, stdout.stderr. Теперь дочерний процесс выполнил программу (скажем, bc). Программа ожидает ввода, когда вы набираете 1 + 1 - она ​​передается ведущему (помните, что дочерний и подчиненный имеет некоторый stdin, stdout, stderr) дочерним / ведомым. master вычисляет свой ответ «2» и записывает в stdout - так как parent ожидает данных от master - он берет «2» и записывает в stdout.

Я пришел к такому заключению, пройдя несколько старых добрых программ на псевдотерминале :) Я не думаю, что логика python не будет отличаться от них. HTH кто-то.

...