Краткий ответ: вы неправильно работаете с псевдотерминалом. Наблюдение странных или даже случайных результатов при чтении внешним процессом с псевдотерминала является нормальным; вы не должны этого делать. Это как если бы два человека одновременно писали на одной клавиатуре. (То, что вы можете видеть это в некоторых телешоу, не означает, что это имеет какой-либо смысл.)
Длинный ответ: измените свой подход, и вы получите гораздо лучшие результаты.
Рассмотрим следующую задачу, которую вы можете выполнить, чтобы ознакомиться с псевдотерминальным поведением:
Создайте главный псевдотерминал и разрешите подчиненному доступ к нему.
(Используйте posix_openpt()
, grantpt()
и unlockpt()
для создания псевдотерминала. Используйте ptsname()
, чтобы узнать имя устройства подчиненного конца.)
Разветвить дочерний процесс.
(Используйте fork()
, чтобы разветвить дочерний процесс, затем setsid()
для отключения от управляющего терминала. Также создается новая группа процессов, поэтому ваш основной процесс может отправлять сигналы всем процессам, запущенным дочерним процессом, отправляя сигналы всей группе.)
В дочернем процессе откройте стандартный ввод (STDIN_FILENO
) для чтения с подчиненного псевдотерминала и стандартный вывод (STDOUT_FILENO
) и стандартную ошибку (STDERR_FILENO
) для записи на подчиненный конец псевдотерминала. Выполнить nano
.
(Используйте dup2()
, чтобы скопировать дескрипторы на нужные места, close()
, чтобы закрыть лишние, и, например, execlp("nano", "nano", NULL)
для выполнения nano
. Обратите внимание, что первое "nano"
— это имя файла команды nano, а второй — это параметр argv[0]
, который видит команда. Он не предоставляет никаких фактических параметров командной строки; он действует так, как если бы вы запускали nano
в своей любимой оболочке.)
В родительском процессе теперь вы можете читать и писать на главный конец псевдотерминала.
Обратите внимание, что вам, возможно, придется делать это одновременно; нет никакого способа узнать, когда вы можете/нужно/должны читать (больше), а когда запись может заблокировать.
Я не могу не подчеркнуть, насколько важно здесь быть полнодуплексным или неблокирующим. Если вы никогда не читали со своего псевдотерминала, не ждите, что он тоже сработает.
В родительском процессе удалите файл foobar.txt
.
(Используйте remove()
или unlink()
.)
Это делается для того, чтобы nano
не вызывало диалоговое окно "Файл уже существует".
В родительском процессе при чтении любого вывода подчиненный процесс может записать на псевдотерминал,
Подождите долю секунды (пока nano рисует экран редактора)
Напишите Some text
и возврат каретки \r
,
Подождите долю секунды,
Напишите Ctrl+O (\017
, часто визуализируется как ^O
)
Напишите foobar.txt
и возврат каретки \r
,
Подождите долю секунды,
Напишите Ctrl+X (\030
, часто визуализируется как ^X
),
Ждать
и nano
должен выйти.
В родительском процессе дождитесь выхода дочернего (nano
) процесса.
(Для этого используйте цикл и waitpid()
.)
Если вы сделаете все вышеперечисленное, ваша основная программа управления терминалом просто эмулирует локального или удаленного «человека», выполняющего очень короткий сеанс nano
, записывая только Some text
и новую строку, сохраняя его в foobar.txt
и закрываясь. (Файл должен содержать "Some text\n\n"
, потому что так работает nano
.)
Шаг 6 легче всего выполнить, если вы создадите вспомогательный поток, который ничего не делает, кроме чтения из дескриптора главного псевдотерминала. В очень ясном смысле он действует как автоматический слив. В конце концов, нас не особо интересует, что здесь nano
выводит на терминал. После шага 7 вы просто закрываете этот дескриптор, в результате чего вспомогательный поток выдает ошибку (read()
возвращает -1 с errno == EBADF
) и возвращается, поэтому основной поток может использовать pthread_join()
, чтобы пожинать плоды.
Конечно, вы можете реализовать шаг 6, используя неблокирующий ввод-вывод. Каким бы способом вы это ни делали, крайне важно, чтобы вы всегда read()
работали с главного псевдотерминала и не попадали в тупик, write()
подключаясь к нему, пока подчиненный процесс также записывает данные на терминал. Бьюсь об заклад, это ситуация, с которой ОП борется.
Типичная последовательность сообщений, проходящих через псевдотерминал в приведенном выше сценарии, такова:
Slave -> Master: "\e[?1049h\e[1;24r\e(B\e[m\e[4l\e[?7h\e[?12l\e[?25h"
Slave -> Master: "\e[?1h\e=\e[?1h\e=\e[?1h\e="
Slave -> Master: "\e[39;49m\e[39;49m\e(B\e[m\e[H\e[2J\e(B\e[0;7m"
" GNU nano 2.2.6 "
" New Buffer "
"\e[23;1H^G\e(B\e[m Get Help "
"\e(B\e[0;7m^O\e(B\e[m WriteOut "
"\e(B\e[0;7m^R\e(B\e[m Read File "
"\e(B\e[0;7m^Y\e(B\e[m Prev Page "
"\e(B\e[0;7m^K\e(B\e[m Cut Text "
"\e(B\e[0;7m^C\e(B\e[m Cur Pos"
"\015\e[24d\e(B\e[0;7m^X\e(B\e[m Exit"
"\e[14G\e(B\e[0;7m^J\e(B\e[m Justify "
"\e(B\e[0;7m^W\e(B\e[m Where Is "
"\e(B\e[0;7m^V\e(B\e[m Next Page "
"\e(B\e[0;7m^U\e(B\e[m UnCut Text"
"\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[3d"
Master -> Slave: "Some text\015"
Slave -> Master: "\e[1;71H\e(B\e[0;7mModified\015\e[3d\e(B\e[mSome text\015\e[4d"
Master -> Slave: "\017"
Slave -> Master: "\e[22d\e(B\e[0;7mFile Name to Write: "
" "
" "
"\e[23;14H\e(B\e[m "
"\e(B\e[0;7mM-D\e(B\e[m DOS Format "
"\e(B\e[0;7mM-A\e(B\e[m Append "
"\e(B\e[0;7mM-B\e(B\e[m Backup File"
"\e[24;2H\e(B\e[0;7mC\e(B\e[m Cancel "
"\e(B\e[0;7mM-M\e(B\e[m Mac Format "
"\e(B\e[0;7mM-P\e(B\e[m Prefix\e[K\e[22;21H"
Master -> Slave: "foobar.txt\015"
Slave -> Master: "\e[1;31H\e[39;49m\e(B\e[0;7mFile: foobar.txt"
"\e[1;71H \e[22;31H\e(B\e[m\e[1K "
"\e(B\e[0;7m[ Wrote 2 lines ]"
"\e(B\e[m\e[K\e[23;14H\e(B\e[0;7m^O\e(B\e[m WriteOut "
"\e(B\e[0;7m^R\e(B\e[m Read File "
"\e(B\e[0;7m^Y\e(B\e[m Prev Page "
"\e(B\e[0;7m^K\e(B\e[m Cut Text "
"\e(B\e[0;7m^C\e(B\e[m Cur Pos"
"\e[24;2H\e(B\e[0;7mX\e(B\e[m Exit "
"\e(B\e[0;7m^J\e(B\e[m Justify "
"\e(B\e[0;7m^W\e(B\e[m Where Is "
"\e(B\e[0;7m^V\e(B\e[m Next Page "
"\e(B\e[0;7m^U\e(B\e[m UnCut Text"
"\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[4d"
Master -> Slave: "\030"
Slave -> Master: "\e[23d\e[J\e[24;80H"
Slave -> Master: "\e[24;1H\e[?1049l\015\e[?1l\e>"
где \e
является сокращением для \033
или \x1B
, т.е. символ ASCII ESC.
Обратите особое внимание на то, как подчиненный процесс nano
извергает все виды вывода, просто чтобы нарисовать причудливый экран редактора. Если бы были часы или что-то в этом роде, которые регулярно менялись, они бы изрыгали эти обновления каждую секунду.
Причиной того, что Master->Slave использует \r
вместо \n
в качестве новой строки, является по умолчанию. настройки термиос.
person
Nominal Animal
schedule
07.04.2015
fcntl()
переопределяет первый. Вы также можете просто поставить флагиopen()
, знаете ли. 3) Псевдотерминал - это не файл, это псевдотерминал и ведет себя как псевдотерминал. Прочтитеman 7 pty
. Вы можете использовать его для запуска nano или какого-либо приложения на основе Curses; пытаться это сcat
просто глупо. - person Nominal Animal   schedule 07.04.2015cat </dev/pty/N
совпадает сcat /dev/stdin
. Если ваш мастер никогда не читает выходные данные подчиненного устройства, подчиненное устройствоcat
будет блокироваться на неопределенный срок при записи. - person Nominal Animal   schedule 07.04.2015read()
из блока псевдотерминала (подчиненного конца) не так уж удивительно. - person Nominal Animal   schedule 07.04.2015