Less получает ввод с клавиатуры из stderr?

Я просматриваю код утилиты «less», в частности, как она получает ввод с клавиатуры. Интересно, что в строке 80 файла ttyin.c он устанавливает файловый дескриптор для чтения:

     /*
      * Try /dev/tty.
      * If that doesn't work, use file descriptor 2,
      * which in Unix is usually attached to the screen,
      * but also usually lets you read from the keyboard.
      */
  #if OS2
      /* The __open() system call translates "/dev/tty" to "con". */
      tty = __open("/dev/tty", OPEN_READ);
  #else
      tty = open("/dev/tty", OPEN_READ);
  #endif
      if (tty < 0)
          tty = 2;

Разве дескриптор файла не 2 stderr? Если да, то ЧТО?! Я думал, что ввод с клавиатуры был отправлен через стандартный ввод.

Интересно, что даже если вы сделаете ls -l * | less, после завершения загрузки файла вы все равно сможете использовать клавиатуру для прокрутки вверх и вниз, но если вы сделаете ls -l * | vi, тогда vi будет орать на вас, потому что он не читает со стандартного ввода. Какая большая идея? Как я оказался в этой странной новой стране, где stderr — это и способ сообщения об ошибках на экране, и способ чтения с клавиатуры? Я не думаю, что я больше в Канзасе...


person Michael    schedule 17.09.2009    source источник
comment
Кстати, если вы напишете ls -l * | vim -, vim проделает аналогичную магию.   -  person ephemient    schedule 07.02.2011


Ответы (4)


$ ls -l /dev/fd/
lrwx------ 1 me me 64 2009-09-17 16:52 0 -> /dev/pts/4
lrwx------ 1 me me 64 2009-09-17 16:52 1 -> /dev/pts/4
lrwx------ 1 me me 64 2009-09-17 16:52 2 -> /dev/pts/4

При входе в интерактивный терминал все три стандартных файловых дескриптора указывают на одно и то же: ваш TTY (или псевдо-TTY).

$ ls -fl /dev/std{in,out,err}
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stdin -> fd/0
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stdout -> fd/1
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stderr -> fd/2

По соглашению мы читаем из 0 и записываем в 1 и 2. Однако ничто не мешает нам поступить иначе.

Когда ваша оболочка запускает ls -l * | less, она создает канал от файлового дескриптора 1 ls к файловому дескриптору 0 less. Очевидно, что less больше не может читать ввод пользователя с клавиатуры из файлового дескриптора 0, он пытается вернуть TTY, как может.

Если less не был отсоединен от терминала, open("/dev/tty") передаст ему телетайп.

Однако, если это не удается... что вы можете сделать? less делает последнюю попытку получить TTY, предполагая, что дескриптор файла 2 присоединен к тому же объекту, к которому был бы присоединен дескриптор файла 0, если бы он не был перенаправлен.

Это не является отказоустойчивым:

$ ls -l * | setsid less 2>/dev/null

Здесь less предоставляется собственный сеанс (поэтому он больше не является частью активной группы процессов терминала, что приводит к сбою open("/dev/tty")), а его файловый дескриптор 2 был изменен, теперь less завершается немедленно, потому что он еще выводит на TTY он не может получить какой-либо пользовательский ввод.

person ephemient    schedule 17.09.2009
comment
О, я вижу это сейчас. Поскольку stderr — это не что иное, как дескриптор файла, который фактически подключен к терминалу, он может читать или писать из него по своему усмотрению. ЭТО КРУТО! Спасибо, эфемер. - person Michael; 18.09.2009
comment
Но разве файловый дескриптор 2 не будет открыт только для записи? - person Blacklight Shining; 11.02.2014
comment
@BlacklightShining Ваш эмулятор терминала, вероятно, запустил вашу оболочку, используя что-то вроде forkpty< /a>, который создает один файловый дескриптор для чтения и записи для псевдо-TTY и дублирует его на 0,1,2. - person ephemient; 13.02.2014
comment
чего я не понимаю, так это того, что если он читает из /dev/tty, он не получает кучу данных из стандартного ввода (производитель в конвейере)? поэтому я предполагаю, что он ищет определенные специальные нажатия клавиш, такие как / ? - person Alexander Mills; 07.07.2019

Ну... во-первых, вы, кажется, пропустили вызов open(), который открывает '/dev/tty'. Он использует файловый дескриптор 2 только в том случае, если вызов open() не удался. В стандартной системе Linux и, возможно, во многих системах Unices существует '/dev/tty', который вряд ли вызовет сбой.

Во-вторых, комментарий вверху содержит ограниченное количество объяснений того, почему они возвращаются к файловому дескриптору 2. Я предполагаю, что stdin, stdout и stderr в любом случае в значительной степени связаны с '/dev/tty/', если только они не перенаправлены . И поскольку наиболее распространены перенаправления для stdin и/или stdout (через конвейер или </>), но реже для stderr, вероятность того, что использование stderr, скорее всего, по-прежнему будет подключаться к «клавиатуре».

person Jason Musgrove    schedule 17.09.2009
comment
Причина использования stderr заключается в том, что stdin/stdout, скорее всего, являются каналами, созданными порождающей оболочкой. Передача в меньшее или из меньшего - это ноуп, но он работает. Но перенаправление stderr команды less имеет мало значения и вряд ли будет выполнено. Так что ставка на то, что stderr действительно является терминальным устройством, является разумным предположением. - person Andy Ross; 18.09.2009

Тот же вопрос с ответом от человека, который его задал, находится на linuxquestions, хотя они цитируют источник, немного отличающийся от less. И нет, я не понимаю большую часть этого, поэтому я не могу помочь, кроме этого :)

person Troubadour    schedule 17.09.2009

Похоже, что это специфичная для Linux функция, которая отправляет ввод с клавиатуры в FD 2.

person DVK    schedule 17.09.2009
comment
Откровенно ложный. Попробуйте это на любом другом UNIX. - person ephemient; 18.09.2009