Отказ от ответственности: это тирада, но я хочу сказать, что это полезная тирада. Смысл этой статьи в том, чтобы злиться на вещи, предлагать решения и исследовать их. Однако я не буду полностью исследовать предлагаемые решения, поэтому они могут показаться недоработанными. Я не намерен слишком зацикливаться на их деталях, а хочу стимулировать ваш разум и попытаться поколебать некоторые из глупых статус-кво.

Да, и это тоже не так долго, как кажется. В нем есть несколько изображений и дополнительное приложение. Будет весело, обещаю!

Стандартные потоки

Подумайте о программе, которую вам пришлось написать. Поскольку вы настоящий Unixer, вы принимаете ввод со стандартного вывода на стандартный вывод. В этом есть прекрасная простота, которая еще больше усиливается трубами. Трубы действительно красивы. Не шутка. Это потрясающая концепция, и концептуально они имеют смысл. Не во многих вещах это есть.

Итак, давайте посмотрим на программу, которую все мы знаем и любим, curl:

% curl -vvv http://api.icndb.com/jokes/random | jq '.value.joke'
"Two wrongs don't make a right. Unless you're Chuck Norris. Then two wrongs make a roundhouse kick to the face."

И curl от души отвечает с IP-адресами, к которым он подключается, заголовками запросов и ответов и даже индикаторами выполнения! Наконец, результат передается в jq, где он может развлечься и получить нашу шутку.

Задержите эту мысль. Здесь что-то не так.

Как curl сообщал, какие данные (HTTP-ответ) нужно передать конвейеру, а какая информация предназначена для пользователя (индикаторы выполнения и др.)? В конце концов, jq не интересует индикатор выполнения запроса, но пользователь уверен.

Скорее всего, вы знаете ответ: stderr. stdout - это то, что передается команде по конвейеру, в то время как stderr, который не обрабатывается конвейером, вместо этого обрабатывается вашей оболочкой. Мы можем явно обрабатывать stderr следующим образом:

% curl -vvv http://api.icndb.com/jokes/random 2>/dev/null | jq '.value.joke'
"Chuck Norris can win at solitaire with only 18 cards."

По соглашению 2 - это дескриптор файла stderr, поэтому мы можем указать нашей оболочке перенаправить его в какой-либо файл. Теперь ты этого не увидишь.

Возможно, вы не заметили, что здесь «не так», особенно если вы пользователь оболочки с большим опытом. Призываю еще раз перечитать предыдущие пара абзацев.

Не торопитесь.

Что за херня

Как мы можем использовать stderr в качестве соглашения для обозначения вывода, предназначенного для пользователя? Его буквально называют «стандартная ошибка», и мы превратили его в также, что означает «полностью допустимый вывод».

Я пытался придумать подходящую аналогию, но потерпел неудачу, поскольку, похоже, единственным подходящим вариантом является ресторан с блюдом под названием «ударь меня по лицу», которое иногда представляет собой восхитительное суфле, а иногда приносит вам удар в лицо.

Это не просто стандарт. stdin также неисправен:

% echo 4 | read -E
4

У вас есть программа, которая принимает ввод от стандартного ввода, но также требует ввода пользователя («вы уверены?», Многоступенчатый ввод и т. Д.)? Жаль, если ты трубишь!

Да, вы можете манипулировать / dev / tty и друзьями. Но не в этом дело. Вы также можете использовать проклятия, или написать графический интерфейс, или сервер, или вы можете просто начать и отправиться на Бермудские острова или Багамы (давай, мама).

Дело в том, что святое трио stdin, stdout и stderr ошибочно. Они объединяют две различные концепции: программное взаимодействие и пользовательское взаимодействие. Нам нужны каналы для программного управления данными и терминал для интерактивного управления данными.

Скромное предложение

Добавляем еще два потока: userin и userout. Каналы связываются между stdin и stdout, userin и userout привязаны к терминалу.

Если перед вами нет канала и вы читаете из stdin, значит, вы читаете с терминала, который является userin.
Если вы не подключены к терминалу, все идет как обычно: чтение будет заблокировано навсегда, запись не имеет никакого эффекта.

Вот некоторые возникающие сложности:

  1. Как мы и должны ли перенаправлять userin или userout на другие каналы?
  2. Все, что жестко кодирует fds 3 или 4, будет прикручено.
  3. А как насчет таких программ, как apt-get, которые вы обычно комбинируете с yes? Если они принимают ввод от userin, как yes будет писать ему, учитывая пункт 1 в этом списке? Честно говоря, мне их не очень жаль. Это должен быть флаг, который вы передаете программе. да - это взлом.
  4. Однако это вызывает правильное решение программирования: что мы принимаем от stdin, а что от userin? Прямого ответа нет, но, если подумать, он у вас уже есть: структурированный ввод, который подходит для программ (например, форматы файлов инициализации, html или любые другие структурированные данные), поступает из стандартного ввода. Человеческий ввод (например, подсказки, запросы и т. Д.) Исходит от пользователя.
    Надо обдумать.
  5. Тот же вопрос касается stdout и userout. Моя интуиция подсказывает мне, что это тоже не такое уж сложное решение: используйте userout всякий раз, когда вам кажется, что вам нужно написать в stderr, но это не означает ошибки. stdout по-прежнему выводится вашей программой, и он не изменился. userout выводится явно, предназначенным для пользователя.
  6. А как насчет ssh? А как насчет tmux?
  7. Это не просто изменение libc для включения этих переменных - терминал должен взаимодействовать. Что происходит, когда вы используете последнюю версию libc, но используете несовместимый терминал? Какова же тогда семантика userin и userout?

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

Мне очень нравится эта идея, потому что она добавляет красоты, не нарушая при этом всего: программы по-прежнему взаимодействуют через каналы. И это опция opt-in - программы, написанные до введения userin и userout, ничего не почувствуют, программы, написанные после, наконец, не могут записывать вывод пользователя в stderr и знают, что они могут получать ввод пользователя от userin.

Выход для людей

Но хватит об этом. Перейдем к еще более спорной теме.

Сейчас 2016 год, и я все еще не могу просматривать изображения на своем терминале, файл с выделенным синтаксисом, интерактивно читать PDF-файл или строить график. Тенденция здесь в том, что вы можете просматривать только текст и ничего больше.

Так не должно быть. Мне становится холодно только от того, что я написал эти строки, но что, если ... что, если ... терминал не был ограничен текстом?

Будущее здесь, и его подсказка - нянский кот. Терминология реализует все, о чем я просил, и даже больше. На картинке выше я набрал tycat 3d-save.png, и появилось изображение, на что я жалуюсь, верно?

Проблема в стандартах. Нам нужен стандартный способ сообщить нашему терминалу, что здесь вывод - изображение, видео, PostScript или файл кода, пожалуйста, отформатируйте его соответствующим образом. Одного терминала, реализующего отдельную команду для такого вывода, недостаточно. Тот парень или девушка, которые только что завершили установку Ubuntu в качестве своего первого дистрибутива Linux, должны иметь возможность запускать gnome-terminal и cat образ кошки.

Помните, что с добавлением userout у нас есть способ сообщить, что часть информации предназначена для пользователя, а не для случайной программы, поэтому конвейер не является нашей большой проблемой.

Это не прогулка по парку. В очередной раз перед нами список проблем:

  1. Как мы сообщаем тип вывода? Как мне сказать, что я собираюсь написать SVG или OGG?
  2. Как нам написать несколько вещей разного характера? Если у меня есть видео, которое я хочу воспроизвести, а также гифка для отображения, нужно ли мне полностью записывать видео, прежде чем начинать писать гифку?
  3. Какие форматы поддерживаются? Зависит ли реализация списка? Есть минимальные требования? Есть ли список канонических реализаций для различных форматов? Как выбрать одно из них?
  4. А как насчет управления? Можете ли вы воспроизводить / приостанавливать / отключать / зацикливать видео? Обязательно ли иметь такую ​​поддержку? Каковы минимальные необходимые элементы управления? Есть ли у гифок элементы управления?
  5. А как насчет screen и tmux? Я подключаюсь и в моем SuperAwesomeTerm cat образ, но затем подключаюсь к OldGrumpyTerm. Мало того, я затем решаю подключиться к себе по ssh. Что происходит?
  6. Как его выключить? Я действительно хочу увидеть двоичный объект изображения.
  7. Я Боб, и я написал BobTerm. Вы заставляете меня справляться со всеми ужасными вещами! Раньше это был простой текст, но теперь я должен знать, как выводить случайные двоичные форматы! Libvte даже не поддерживает это (… пока).
  8. Разве это не устаревшие вещи вроде mplayer или feh? Если вы можете cat изображение, тогда зачем вам feh для? На самом деле, разве это не дает терминалу слишком много энергии?

Самая простая проблема, которую нужно решить, - это указать формат файла. Для этого у нас есть MIME. Но дело не в том, чтобы просто написать заголовок mime, за которым следует новая строка: что, если мы не хотим отправлять заголовок mime? Что, если мы не отправляем mime-заголовок, но наш вывод выглядит точно так же, как mime-заголовок?

Относительно простой способ справиться с 1 и 2 - это иметь возможность «разветвлять» поток. Псевдокод C:

FILE *userout = getuserout();
FILE *video_stream = forkstream(userout, “video/ogg”);
FILE *gif_tream = forkstream(userout, “image/gif”);

Это позволит нам указать тип пантомимы и без проблем передавать файл. Лучше, чем это было бы гарантировать, что запись в gif_stream с одновременной записью в video_stream не вызовет коллизии. Это будет сложно реализовать, но я думаю, оно того стоит.

К сожалению, нам нужно заняться 3, 4 и 5. А как насчет терминалов, которые этого не поддерживают? Какое минимальное количество форматов необходимо для соответствия? А как насчет нескольких терминалов?

Давайте на мгновение сделаем боковую панель, боковую панель, которая, я думаю, уместна не только для этого момента или этой напыщенной речи, но и для всей серии выступлений. Вместо того, чтобы слушать мой лепет (который, я уверен, вам уже надоел), я рекомендую вам послушать, как кто-то умнее меня говорит умные вещи. Настройтесь, пока он не покажет слайд «Должны ли веб-сайты выглядеть одинаково во всех браузерах». Может быть, больше. Вы все равно должны посмотреть эту лекцию. Николас Закас о прогрессивном улучшении:

Решили пропустить или просто хотите увидеть, как я говорю? Ах ты, льстец. Вот краткое изложение того, что он сказал:

Раньше в телевизорах отображалось только черно-белое изображение. Затем люди начали делать цветные телевизоры, а затем и телевизоры высокой четкости. Несмотря на очень разные возможности, все они способны отображать один и тот же контент: вы подключаете их, и они показывают вам сотый повтор Друзья.

Вопрос, который я до сих пор не осознал, заключается в том, не возлагает ли это слишком большую ответственность на терминал. Я думаю, ответ - нет. Терминал должен поддерживать именно то, что он хочет поддерживать, а не какой-либо формат. Но у него должна быть такая основная возможность: программы должны иметь возможность сигнализировать пользователю о том, что они показывают.

Это возможно. Мы занимались этим в другом месте. Мы просто пренебрегли терминалом.

Давай перестанем пренебрегать терминалом

Рыба сотворила с твоей раковиной удивительные вещи. У вас есть предложения по завершению, цвета и так много вещей, которые имеют такой смысл, что вы задаетесь вопросом, зачем кому-то по-прежнему добровольно использовать bash !? Zsh, который является абсолютным чудовищем, все еще не является повсеместным, даже несмотря на то, что это прямая замена bash.

Почему? Почему в вашем дистрибутиве нет zsh или fish? Честно говоря, некоторые так и делают. Я могу говорить об Arch, установочный образ которого идет с настроенным zsh. Но это капля в море. Почему Ubuntu не использует zsh по умолчанию? Почему CentOS не дает вам рыбу?

Я не знаю, почему мы так поступаем с собой. Ваш экран потрясающий. Вы просматриваете HD-фильмы на нем. Но когда вы работаете в терминале, у вас, вероятно, даже нет сглаженных шрифтов.

То, что у нас почти было

Пока я писал это, я искал свои идеи в Google. Невозможно, чтобы я единственный, кто имел безумные идеи добавить стандартные потоки или сделать оболочку менее ужасной. И действительно, появилось два популярных проекта: FinalTerm и TermKit.

FinalTerm затрагивает некоторые из вопросов, которые я поднял в разделе о терминалах. Кроме того, он никогда не выходил из альфы, и он также страдает серьезным недостатком - быть мертвым.

TermKit поразил меня, как в лучшую, так и в худшую сторону. Он попал в то, к чему я стремился, а затем и к некоторым. Он также страдает тем же симптомом смерти. Я не собираюсь здесь описывать все, поскольку на этой странице Reddit был опубликован пост-мортерм.

Неудача этих проектов меня не радует. FinalTerm умер из-за технологических причин наряду с обычными причинами OSS (когда ведущий разработчик демотивирован, кто берет бразды правления?), А TermKit умер как по технологическим причинам, так и из-за того, что он пытался сделать слишком много вещей за слишком короткий промежуток времени. Это не мотивирует.

Путь вперед

Больше всего меня огорчает то, что я просто не знаю, что мне делать с этими идеями. Даже если бы мне пришлось реализовать свою собственную оболочку и терминал, этого было бы недостаточно - все это требует сотрудничества со стороны разработчиков программ. Возможно, уже слишком поздно, и мы всегда будем в том состоянии, в котором находимся сегодня. Это не так. Это просто ... тьфу.

Фу.

Я не знаю, что будет дальше.

Обновление: что я сделал дальше

Я начал работать над эмулятором терминала под названием plex. Это не будет полное TE, а будет только POC идей, представленных здесь (надеюсь).

Посмотрим, к чему это приведет.

Приложения

Но подождите ... как вы думаете, куда вы собираетесь? Мы еще не закончили. В этом разделе подробно описаны проблемы, решения которых у меня нет, или мои мысли по этому поводу еще слишком незрелы, чтобы иметь значение. Не стесняйтесь пропустить этот раздел или всю статью! Может быть, слишком поздно делать последнее.

Синтаксис оболочки и getopt

Давайте начнем с чего-нибудь нового: передачи аргументов командам.

# It always starts out simple:
% ls something
% ls -l something
% ls --format=long
# wait, or was it
% ls --format long
# what about spaces
% ls ’01. Serenity.mp4'
# and quotes
% ls “I’m a teapot”
% ls I\’m\ a\ teapot
# now you’ll want to kill yourself
% grep -EIrn ‘can \’you [\\doubt] \”w\\/hy’

Я даже не знаю, верен ли последний пример. У меня не хватает духу пройти через это.

У вас есть множество способов передать аргументы команде, некоторые из которых должны иметь префикс в определенном формате. Некоторым флагам требуется одинарное тире, некоторым - двойное. И есть много противоречивой логики: обычно двойное тире ставит префикс многобуквенного слова, но иногда это не так (например: mplayer -sub subtitle-path, но также mplayer --help , а find - повторное нарушение). Иногда вы можете указать несколько однобуквенных аргументов в одном тире, как grep, но иногда вещи просто странные, как в head -20 path, который обрабатывает числовой аргумент после тире означает -n, и этот список можно продолжить.

И разве вы не заставили меня начать с find -exec и того, как вам нужно там жонглировать, не говоря уже о конвейере и передаче вещей в sh -c!

Да, и давайте не забываем --help, который иногда работает, но иногда не обрабатывается. Представьте, насколько мощным он был бы, если бы ваша оболочка могла разумно выводить аргументы программы, сначала запустив ее с помощью - help и проанализировав результаты, предоставив вам интеллектуальные подсказки и дополнения. Но, конечно, результаты использования не стандартизированы, так что вы можете забыть об этом. Его несколько смягчают встроенные парсеры аргументов, такие как argparse Python или optparse Ruby, но вы никогда не увидите ничего подобного в составе libc.

Есть еще то, насколько странным является расширение переменных. Это похоже на тупой текстовый макрос. Мы заслуживаем гораздо лучшего.

fish как бы улучшает это, и по пути ломает много вещей (что и хорошо, и плохо). Но я не думаю, что они зашли достаточно далеко. Я хочу хороший язык программирования.

А теперь скажите мне: вы когда-нибудь запускали ipython?

Это слишком ошеломляющий опыт. Если бы я был посмелее, я бы сделал chsh ipython. Это больше похоже на то, что мы заслуживаем. Мы заслуживаем надлежащего языка программирования с разумным синтаксисом для конвейеров и переменных и реальной помощью.

Мы никогда этого не получим.

Форматирование вывода

Допустим, у вас есть две программы, и они хотят отправлять данные друг другу. Чтобы немного облегчить ситуацию, вы пропускаете оба через оболочку, чтобы вытащить их. Один из самых известных примеров - ps и grep:

% ps aux | grep dbus

И снова здесь что-то не так. То, что я тонко упомянул в моем первом примере с curl и jq. Программы не отправляют друг другу структурированные, удобные для машины данные. Из-за свертки между выводом программы и выводом человека программы со временем приняли свои собственные соглашения о выводе, и вы, потребитель вывода, остаетесь сами по себе разбираться в том, что здесь происходит.
Для пояснения: ps 'не так легко читать и анализировать на компьютере. Наша программа должна знать и о том, что мы принимаем ввод от ps, и о том, как его анализировать.

Это и хорошо, и плохо. Это хорошо, потому что ваша программа выводит текст произвольной формы - это просто потрясающе. Это плохо, потому что с вашей программой выводить текст произвольной формы сложно.

Решить эту проблему непросто. Большинство людей говорят: «Просто начните передавать объекты вместо текста», но это не так просто, как кажется. Семантика связи между программами - сложная проблема. Вот некоторые сложности:

  1. Изменение этого ломает все. Без какого-то хакерского решения программам нужно будет знать, когда программа, которая вчера записывала выходные данные в формате X, теперь записывает их в формате Y. Наиболее легко решить с помощью флагов, но это просто так.
  2. Для этого нужно изменить все. От ls до ssh до mount до strace. Это огромное изменение, которое не произойдет в одночасье, а это значит, что внедрение будет медленным.
  3. Что произойдет, если вы напишете структурированный вывод в стандартный вывод? Вы же не хотите, чтобы пользователи видели уродливые необработанные данные.
  4. Вы теряете много свободы. Если вы привязаны к формату вывода и хотите выразить то, чего в нем нет, вы застряли на хитростях.
  5. Кстати, я не сказал, какой формат мы будем использовать. JSON? MessagePack? "БЕРТ"? Cap’n Proto? Кто-нибудь еще из миллионов? Как мы выберем одно из них?
    Если мы выберем строго типизированные решения, такие как Protobuf или Thrift, то как передать наши схемы?
    Если мы выберем решения со слабым типом, такие как S- Выражения остались проблемы проверки и десериализации, что указывает на следующую проблему:
  6. Как мы собираемся реализовать сериализацию и десериализацию? В разных языках существуют совершенно разные концепции о типах и значениях. Как написать действительно большое целое число по сети, не взорвав соседнюю программу, которая не справится с этим без библиотеки BigDecimal? А как насчет дат и случайных предметов? Можете ли вы отправлять функции?
  7. Кто отвечает за проверку? Конечно, программы всегда должны очищать свои входные данные, но что произойдет, если программа выведет неверный результат? Оболочка сурово разговаривает с ним? Будет ли он просто кататься вместе с ним?
  8. Как только формат выбран, мы практически застреваем навсегда. Новые языки должны будут реализовать эту сериализацию и десериализацию. Кто будет писать все эти парсеры? Писать парсеры сложно.
  9. Не говоря уже о потоковой передаче - наш формат сериализации данных должен быть потоковым, чтобы правильно использоваться через каналы.
  10. Как это влияет на многочисленные API Linux, доступ к которым осуществляется через файлы, подобные тем, которые находятся в дереве proc? Будут ли изменены / proc / mount и / proc / net / arp и т. Д. Для получения этого сериализованного вывода? Это основной источник обратной несовместимости.
  11. К черту это дерьмо, я ухожу.

Вы можете подумать, что Powershell уже решил эту проблему. Это все равно что сказать, что Python решил эту проблему - Powershell обменивается данными только между программами, написанными на определенной платформе, а не посредником общего назначения между совершенно разными платформами. Как Java передает объекты даты в Erlang? Как Io (использующий прототипное наследование) отправляет объекты в C?

Это сложная проблема. Тот, который сегодня не решится. Более умному человеку, говорящему умные вещи, я рекомендую посмотреть фильм Джо Армстронга «Как и зачем соединять вещи вместе»:

Хакерский полдень - это то, с чего хакеры начинают свои дни. Мы часть семьи @AMI. Сейчас мы принимаем заявки и рады обсуждать рекламные и спонсорские возможности.

Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!