UNIX-подобные системы содержат кучу костылей. Крах «философии UNIX»

в 3:36, , рубрики: bash, C, GNU/Linux, open source, shell, UNIX, пайк, Разработка под Linux, Ритчи, томпсон

Модераторы, при следующем внеснии мной правки я просто внесу правку в мастер-копию, размещённую на моём компьютере и пересу её сюда. Так что все ваши правки исчезнут. Если хотите сообщите мне об ошибке, используйте стандартный способ (личку). См. также: github.com/limonte/dear-habr/issues/80 .

В первой части статьи перечислю кучу костылей UNIX, и вообще разных недостатков. Во второй — про «философию UNIX». Статья написана наскоро, «полировать» дальше не хочу, скажите спасибо, что написал. Поэтому многие факты привожу без ссылок.


Костыли в UNIX начали возникать ещё с момента появления UNIX, а это было ещё раньше появления не только Windows, но даже вроде бы Microsoft DOS (вроде бы, мне лень проверять, проверяйте сами). Если лень читать, хотя бы просмотрите все пункты, что-нибудь интересное найдёте. Это далеко не полный список, это просто те косяки, который я захотел упомянуть.

  • В самом начале make был программой, которую один человек написал для себя и нескольких своих знакомых. Тогда он, недолго думая, сделал так, что командами воспринимаются строки, которые начинаются с Tab. Т. е. Tab воспринимался отлично от пробела, что крайне некрасиво и нетипично ни для UNIX, ни за его пределами. Он так сделал, потому что не думал, что make будет ещё кто-то использовать кроме этой небольшой группы. Потом появилась мысль, что make — хорошая вещь и неплохо бы включить его в стандартный комплект UNIX. И тогда чтобы не сломать уже написанные мейкфайлы, т. е. написанные вот этими вот десятью людьми, он не стал ничего менять. Ну вот так и живём… Из-за тех десятерых страдаем мы все.
  • Почти в самом начале в UNIX не было папки /usr. Все бинарники размещались в /bin и /sbin. Но потом вся инфа перестала помещаться на тот диск, который был в распоряжении авторов UNIX (Томпсон, Ритчи). Поэтому они достали ещё один диск, создали папку /usr, а в ней — ещё один bin и ещё один sbin. И смонтировали новый диск в /usr. Оттуда и пошло. Так появилась «вторая иерархия» /usr, а потом в какой-то момент ещё и «третья иерархия» /usr/local, а потом ещё и /opt. Как пишет рассказчик этой истории (ссылку лень щас искать): «Не удивлюсь, если когда-нибудь ещё появится /opt/local»
  • sbin изначально означало «static bin», а не «superuser bin», как можно было подумать. И содержал sbin статические бинарники. Но потом sbin стал содержать динамические бинарники, его название потеряло смысл
  • Windows часто ругают за наличие реестра и сообщают при этом, что подход UNIX-подобных систем (куча конфигов) якобы лучше. А между прочим однажды в ext3 (ну или ext4) появилась особенность (является ли это багом, вопрос спорный), из-за которой при резком выключении компа Gnome потерял все свои конфиги в рабочей папке юзера. И разработчик этой ext3/ext4 сказал в обсуждении баг репорта, что Gnome'у надо было использовать что-то вроде реестра для хранения инфы. И это не говоря уж о том, что критичные файлы UNIX (такие как /etc/passwd), который читаются при каждом (!) вызове, скажем, ls -l, записаны в виде простого текста. И эти файлы надо заново читать и заново парсить при каждом вызове ls -l! Было бы гораздо лучше использовать бинарный формат. Или БД. Или некий аналог реестра. Как минимум для вот таких вот критичных для производительности ОС файлов.
  • Two famous people, one from MIT and another from Berkeley (but working on Unix) once met to discuss operating system issues. The person from MIT was knowledgeable about ITS (the MIT AI Lab operating system) and had been reading the Unix sources. He was interested in how Unix solved the PC loser-ing problem. The PC loser-ing problem occurs when a user program invokes a system routine to perform a lengthy operation that might have significant state, such as IO buffers. If an interrupt occurs during the operation, the state of the user program must be saved. Because the invocation of the system routine is usually a single instruction, the PC of the user program does not adequately capture the state of the process. The system routine must either back out or press forward. The right thing is to back out and restore the user program PC to the instruction that invoked the system routine so that resumption of the user program after the interrupt, for example, re-enters the system routine. It is called «PC loser-ing» because the PC is being coerced into «loser mode,» where «loser» is the affectionate name for «user» at MIT.

    The MIT guy did not see any code that handled this case and asked the New Jersey guy how the problem was handled. The New Jersey guy said that the Unix folks were aware of the problem, but the solution was for the system routine to always finish, but sometimes an error code would be returned that signaled that the system routine had failed to complete its action. A correct user program, then, had to check the error code to determine whether to simply try the system routine again. The MIT guy did not like this solution because it was not the right thing.

    — The Rise of «Worse is Better» By Richard Gabriel, doc.cat-v.org/programming/worse_is_better

    Если кратко и своими словами, то в начале разработки UNIX авторы UNIX решили попросту выдавать ошибку из ядра пользовательской программе, если пользовательская программа прервана по сигналу, и на этот сигнал повешен обработчик. Иными словами, если вы перехватили Ctrl-C (т. е. поставили на него обработчик) в своей программе, а юзер за терминалом нажал этот самый Ctrl-C, то ОС выполнит обработчик, а потом вместо простого продолжения того сисвызова, который выполнялся в момент Ctrl-C, просто прервёт его, вернув из ядра в пользовательскую программу EINTR. В результате программисту, пишущему эту программу придётся эту EINTR предусмотреть. А это усложняет этот userspace код. Ценой упрощения кода ядра. Да, нужно было сделать по-другому. Усложнить код ядра и упростить userspace код, который придётся писать всем программистам. Но тому человеку из Беркли из цитаты выше было пофигу. Он фактически сказал: «Да мне пофиг, что все будут страдать, главное, чтоб код ядра попроще был».

    Дальше — больше. Позже в UNIX-системах всё же пофиксили упомянутую особенность, добавив так называемый SA_RESTART. То есть вместо того, чтобы просто всё пофиксить, они добавили специальный флаг. Так мало того, что они это сделали, этот SA_RESTART ещё и не всегда работает! В частности, в GNU/Linux select, poll, nanosleep и др. не продолжают свою работу после перехваченного прерывания даже в случае SA_RESTART!

  • Вообще, конкретные обстоятельства, возникшие во время разработки оригинальной UNIX, сильно оказали на неё влияние. Скажем, читал где-то, что команда cp названа именно так, а не copy, потому что UNIX разрабатывали с использованием терминалов, которые очень медленно выдавали буквы. А потому набрать cp было быстрее, чем copy
  • Вообще, названия утилит UNIX — это отдельная история. Скажем, название grep идёт от выражения g/re/p (ну или похожего) в языке sed. (Ну а cat — от concatenation, я надеюсь, это все и так знали. :) Ну и для кучи: vmlinuz — Linux with Virtual Memory support gZipped.)
  • printf внезапно является далеко не самым быстрым способ вывода информации на экран или в файл. Не знали, да? А дело в том, что printf, как и сама UNIX в целом, был придуман не для оптимизации времени, а для оптимизации памяти. printf каждый раз парсит в рантайме строку формата. Именно поэтому в веб сервере H2O был придуман специальный препроцессор, который переносит парсинг строки формата на этапе компиляции.
  • Когда Кена Томпсона, автора UNIX (вместе с Деннисом Ритчи) спросили, что бы он поменял в UNIX, он сказал, что назвал бы функцию creat (sic!) как create. No comments. Замечу, что позже этот же Кен Томпсон вместе с другими разработчиками оригинальной UNIX создал систему Plan 9, исправляющую многие недостатки UNIX. И в ней эта функция называется create. :) Он смог. :)
  • Ещё одна цитата:

    A child which dies but is never waited for is not really gone in that it still consumes disk swap and system table space. This can make it impossible to create new processes. The bug can be noticed whenseveral & separators are given to the shell not followed by ancommand without an ampersand. Ordinarily things clean themselves upwhen an ordinary command is typed, but it is possible to get into asituation in which no commands are accepted, so no waits are done;the system is then hung.The fix, probably, is to have a new kind of fork which creates aprocess for which no wait is necessary (or possible); also to limit the number of active or inactive descendants allowed to a process.

    cm.bell-labs.com/cm/cs/who/dmr/man22.pdf

    Это цитата из очень раннего манула UNIX. Уже тогда существование зомби-процессов признавалось багом. Но потом на этот баг попросту забили. Понятное дело, что гораздо позже эта проблема всё же была решена. Т. е. в современном GNU/Linux инструменты для убивания зомби-процессов всё же существуют. Но о них мало кто знает. Обычном kill'ом зомби не убиваются. Про существование зомби-процессов все говорят: «It's for design».

  • Ещё немного про уже упомянутый язык C. Вообще язык C разрабатывался одновременно с UNIX, поэтому критикуя UNIX, нужно покритиковать и C тоже. То, что C очень плох, написано много, я не буду повторять все эти аргументы. Там, синтаксис типов плохой, препроцессор ужасен, легко выстрелить себе в ногу, всякие 4["string"], всякие sizeof ('a') != sizeof (char) (в C, не в C++!), всякие i++ + ++i, всякие while (*p++ = *q++) ; (пример из Страуструпа, второе дополненное издание) и так далее и тому подобное. Скажу лишь вот что. В C до сих пор не научились удобно работать со строками. Неудобство работы со строками постоянно приводит к разнообразным проблемам безопасности. И эту проблему до сих пор не решили! Вот относительно свежий документ от комитета C: www.open-std.org/jtc1/sc22/wg14/www/docs/n1969.htm. В нём обсуждается весьма сомнительный способ решения проблемы со строками. И делается вывод, что этот способ плох. Год публикации: 2015. То есть даже к 2015-ому году окончательного решения ещё нет! И это не говоря об отсутствии простой, удобной и мультиплатформенной системы сборки (а не этого монстра autotools, который ещё и не поддерживает винду, и другого монстра cmake, который поддерживает винду, но всё равно монстр), стандартного менеджера пакетов, удобного как npm (js) или carge (rust), нормальной portability library, с помощью которой можно было кроссплатформенно хотя бы прочитать содержимое папки и хотя бы даже главного сайта C, который был бы главной точкой входа для всех новичков и содержал бы в себе не только документацию, но и краткую инструкцию по установке инструментов C на любую платформу, по созданию простого проекта на C, а также содержал бы удобный поиск по пакетам C (которые должны быть размещены в стандартном репозитории) и, главное, был бы точкой сбора user community. Я даже зарегал домен c-language.org в надежде, что когда-нибудь я создам там такой сайт. Эх, мечты, мечты. (У меня ещё cpp-language.org заныкан, бугога. :)) Но всего этого нет. Хоть это и есть у всех популярных языков, кроме C и C++. И даже у Haskell всё это есть. И у Rust. У Rust, у этого выскочки, который, кстати говоря, метит в ту же нишу, что и C. Есть единый конфиг, который одновременно является конфигом проекта, конфигом сборки и конфигом для менеджера пакетов (собственно, cargo — это менеджер проектов и система сборки одновременно). Есть возможность указания в качестве зависимости для данного пакета другого пакета, размещённого где-то в *GIT*, в том числе указание в качестве зависимости напрямую программы на *GITHUB*. Генерация из коробки документации из сорцов, записанной в комментах на *MARKDOWN*. И пакетный менеджер, использующий для версий *SEMVER*. *GIT*, *GITHUB*, *MARKDOWN*, *SEMVER*, короче говоря *BUZZWORDS*, *BUZZWORDS* и ещё раз *HIPSTERS' BUZZWORDS*. И всё сразу из коробки. Прямо вот заходишь на их главный сайт, и вот на тебе на блюдечке. И работает всё одинаково на всех платформах. Несмотря на то, что Rust — это вроде как язык системного программирования, а не какой-нибудь там javascript. Несмотря на то, что в Rust можно байты гонять. И арифметика указателей там есть. Так почему же, у них, у этих выскочек-растовцев эти хипстерские баззворды есть, а у нас сишников их нет? Обыдно. Я помню, один знакомый спрашивает у меня, где посмотреть список пакетов для C/C++. Пришлось сказать ему, что такого единого места нет. Он: «Программисты на C/C++ должны страдать?» Мне нечего было ему ответить. Ах да, забыл ещё одну вещь. Посмотрите, пожалуйста, на прототип функции signal в том виде, в котором он дан в стандарте C: void (*signal(int sig, void (*func)(int)))(int); и попытайтесь его понять.
  • Терминал в UNIX — жуткое legacy. Подробности здесь: catern.com/posts/terminal_quirks.html
  • Имена файлов в файловых системах UNIX (ext2 и пр.) есть просто поток байтов без кодировки. В какой кодировке они будут интерпретированы, зависит от локали. То есть если создать файл на ОС в одной локали, а потом пытаться посмотреть его имя в ОС в другой локали, будет плохо. В виндовом NTFS такой проблемы нет.
  • UNIX shell хуже PHP! Да, да, а вы что, не знали? Сейчас модно ругать PHP. Но ведь UNIX shell ещё хуже. :) Особенно плохим он становиться, если пытаться на нём программировать, ведь полноценным языком программирования он не является. Но даже для своей ниши (скриптинг типичных задач по администрированию) он годится плохо. Виной тому примитивность shell, непродуманность, legacy, куча частных случаев, костылей, бардак с кавычками, бекслешами, специальными символами и повёрнутость shell'а (как и всего UNIX) на простом тексте.
    • Начнём с затравки. Как рекурсивно найти в папке foo все файлы с именем ? Правильный ответ таков: find foo -name '\'. Ну или так: find foo -name \\. Последний вариант вызовет особенно много вопросов. Попробуйте объяснить человеку, плохо разбираемущемуся в UNIX shell, почему здесь нужно именно четыре бекслеша, а не два и не восемь (грамотеи, подскажите, как правильно написать это предложение, пишите в личку). А написать здесь нужно четыре бекслеша, потому что UNIX shell делает backslash expanding, и find тоже его делает
    • Как touch'нуть все файлы в папке foo (и во вложенных)? На первый взгляд, один из способ таков: find foo | while read A; do touch $A; done. Ну, на первый взгляд. На самом деле здесь можно придумать аж 5 нюансов, которые могут испортить нам малину (и привести к проблемам с безопасностью):
      • Имя файла может содержать бекслеш, поэтому нужно писать не read A, а read -r A
      • Имя файла может содержать пробел, поэтому нужно писать не touch $A, а touch "$A"
      • Имя файла может не только содержать пробел, но и начинаться с пробела, поэтому нужно писать не read -r A, а IFS="" read -r A
      • Имя файла может содержать перевод строки, поэтому вместо find foo нужно использовать find foo -print0, а вместо IFS="" read -r AIFS="" read -rd "" A (тут я не совсем уверен)
      • Имя файла может начинаться с дефиса, поэтому вместо touch "$A" нужно писать touch -- "$A"

      Итоговый вариант выглядит так: find foo -print0 | while IFS="" read -rd "" A; do touch -- "$A"; done. Круто, да? И здесь мы, кстати, не учли, что POSIX не гарантирует (я не совсем в этом уверен), что touch поддерживает опцию --. Если учитывать ещё и это, то придётся для каждого файла проверять, что он начинается с дефиса (или что не начинается со слеша) и добавлять в начало ./. Теперь вы поняли, почему скрипты configure, генерируемые autoconf'ом такие большие и трудночитаемые? Потому что этому configure нужно учитывать всю эту муть, включая совместимость с разными shell'ами. (В данном примере для демонстрации я использовал решение с пайпом. Можно было использовать решение с -exec, но это было бы не так эффектно.) (Ладно, хорошо, мы знаем, что имя файла начинается с foo, поэтому оно не может начинаться с пробела или дефиса.)

    • В переменной A лежит имя файла, нужно удить его на хосте a@a. Как это сделать? Может быть так: ssh a@a rm -- "$A" (как вы уже заметили, мы тут уже учли, что имя файла может содержать пробелы и начинаться с дефиса)? Ни в коем случае! ssh — это вам не chroot, не setsid, не nohup, не sudo и не какая-нибудь другая команда, которая передает exec-команду (т. е. команду для непосредственной передачи сисвызовам семейства execve). ssh (как и su) принимает shell-команду, т. е. команду для обработки shell'ом (термины exec-команда и shell-команда — мои). ssh соединяет все аргументы в строку, передаёт строку на удалённую сторону и там выполняет shell'ом. Окей, может быть так: ssh a@a 'rm -- "$A"'? Нет, эта команда попытается найти переменную A на удалённой стороне. А её там нет, потому что переменные через ssh не передаются. Может, так: ssh a@a "rm -- '$A'"? Нет, это не сработает, если имя файла содержит одинарную кавычку. В общем, не буду вас мучать, правильный ответ таков: ssh a@a "rm -- $(printf '%qn' "$A")". Согласитесь, удобно?
    • Как зайти на хост a@a, с него — на b@b, с него — на c@c, с него — на d@d, а с него удалить файл /foo? Ну, это легко:
      ssh a@a "ssh b@b "ssh c@c \"ssh d@d \\\"rm /foo\\\"\"""
      

      Слишком много бекслешей, да? Ну, не нравится так, давайте чередовать одинарные и двойные кавычки, будет не так скучно:

      ssh a@a 'ssh b@b "ssh c@c '''ssh d@d "rm /foo"'''"'
      

      А между прочим, если бы вместо shell'а был Lisp. И там функция ssh передавала бы на удалённую сторону не строку (вот она, повёрнутось UNIX на тексте!), а уже распарсенный AST (abstract syntax tree), то такого ада бекслешей не было бы:

      (ssh "a@a" '(ssh "b@b" '(ssh "c@c" '(ssh "d@d" '(rm "foo")))))
      

      «А? Что? Lisp? Что за Lisp?» Интересно, да? На, читайте: paulgraham.com/avg.html. И другие статьи Грэма. На русском тоже можно найти.

    • Совместим предыдущие два пункта. Имя файла лежит в переменной A. Нужно зайти на a@a, с него — на b@b, далее на c@c, d@d и удалить файл, лежащий в переменной A. Это я оставляю вам в качестве упражнения. :) (Сам я не знаю, как это сделать. :) Ну, может, придумаю, если подумаю.)
    • echo вроде как предназначен, чтобы печатать на экран строки. Вот только использовать его для этой цели, если строчка чуть сложнее, чем «Hello, world!», нельзя. Единственно верный способ вывести произвольную строку (скажем, из переменной A) таков: printf '%sn' "$A".
    • Допустим, нужно направить stdout и stderr команды cmd в /dev/null. Загадка: какие из этих шести команд выполняют поставленную задачу, а какие — нет?
      cmd > /dev/null 2>&1
      cmd 2>&1 > /dev/null
      { cmd > /dev/null; } 2>&1
      { cmd 2>&1; } > /dev/null
      ( cmd > /dev/null ) 2>&1
      ( cmd 2>&1 ) > /dev/null
      

      Оказывается, правильный ответ — 1-я, 4-я и 6-я выполняют, 2-я, 3-я и 5-я — не выполняют. Опять-таки, выяснение причин этого оставляется в качестве упражения. :)

  • Вообще, этот пост появился в ответ на вот этот пост: geektimes.ru/post/285682. Там говорилось, мол, в винде специальная дата используется как метка драйвера от Microsoft. Вместо ввода специального аттрибута или проверки производителя. Особенностей такого рода в UNIX полно. Является ли файл скрытым, выясняется на основе наличия точки в начале файла вместо специального аттрибута. Когда я сам впервые об этом узнал (да, да, в те далёкие времена, когда я впервые поставил Ubuntu), я был шокирован. Я подумал, вот идиоты. А щас привык. Но если вдуматься, это жуткий костыль. Далее, shell выясняет, является ли он login shell'ом на основе дефиса, переданного первым символом в argv[0] (?!). Это abuses (ну или misuses, неправильно использует, не знаю, как по-русски сказать) argv[0]. argv[0] не для этого предназначен. Вместо какого-нибудь другого способа. Любой другой способ был бы красивее. Как угодно, любым другим аргументом, переменной окружения.
  • В BSD sockets юзер вынужден сам менять порядок байт у номера порта. А всё потому, что когда-то давно кто-то допустил в коде ядра UNIX ошибку, не предусмотрев смену порядка байт. И в качестве временного хака исправил user space код вместо кода ядра. Так и живём. Оттуда это и в Windows перешло (вместе с файлом /etc/hosts, он же C:windowssystem32driversetchosts)

«Философия UNIX». Есть мнение, что якобы UNIX прекрасна и идеальна. Что все её основные идеи («всё есть файл», «всё есть текст» и т. д.) прекрасны и составляют так называемую прекрасную «философию UNIX». Так вот, как вы уже начали догадываться, это не совсем так. Давайте разберём эту «философию UNIX» по пунктам. Сразу скажу: я не хочу сказать, что все пункты нужно отменить, просто я указываю на их неуниверсальность.

  • «Всё есть текст». Как мы с вами уже выяснили на примере /etc/passwd, повсеместное использование простого текста может привести к проблемам с производительностью. И вообще, авторы UNIX фактически придумали для каждого системного конфига (passwd, fstab и так далее) свой формат. Со своими правилами экранирования специальных символов. Да, а вы что думали? /etc/fstab использует пробелы и переносы строк как разделители. Но что если имена папок содержат, скажем, пробелы? На этот случай формат fstab'а предусматривает специальное экранирование имён папок. Так что любой скрипт, читающий fstab, оказывается, должен это экранирование интерпретировать. Например, с помощью специально предназначенной для этого утилиты fstab-decode (запускать от рута). Не знали, да? Идите исправляйте свои скрипты. :) В результате для каждого системного конфига нужен свой парсер. И было бы гораздо проще, если бы для системных конфигов использовался вместо этого какой-нибудь JSON или XML. А может быть даже некий бинарный формат. Особенно для тех конфигов, которые постоянно читаются разными программами. И для которых, как следствие, нужна хорошая скорость чтения (а у бинарных форматов она выше).

    Я не закончил по поводу «всё есть текст». Стандартные утилиты выдают вывод в виде простого текста. Для каждой утилиты фактически нужен свой парсер. Часто приходится парсить вывод той или иной утилиты при помощи sed, grep, awk и т. д. У каждой утилиты свои опции для того, чтобы установить, какие именно столбцы нужно выдавать, по каким столбцам нужно сортировать вывод и т. д. Было бы лучше, если бы утилиты выдавали вывод в виде XML, JSON, некоего бинарного формата или ещё чего-нибудь. А для удобного вывода этой информации на экран и для дальнейшей работы с ней можно было бы пайпить результат в дополнительные утилиты, которые убирают те или иные столбцы, сортируют по тому или иному столбцу, выбирают нужные строки и т. д. И либо выводят результат в виде красивой таблички на экран, либо передают его куда-то дальше. И всё это универсальным способом, не зависящим от исходной утилиты, которая сгенерировала вывод. И без необходимости парсить что-либо регексами. Да, UNIX shell плохо работает с JSON и XML. Но ведь у UNIX shell полно других недостатков. Нужно выкинуть его вовсе и заменить на некий другой язык, который помимо всего прочего может удобно работать со всякими JSON.

    Вы только представьте! Вот допустим, нужно удалить все файлы в текущей папке с размером, большим 1 килобайта. Да, я знаю, что такое надо делать find'ом. Но давайте предположим, что это нужно сделать непременно ls'ом. Как это сделать? Вот так: LC_ALL=C ls -l | while read -r MODE LINKS USER GROUP SIZE M D Y FILE; do if [ "$SIZE" -gt 1024 ]; then rm -- "$FILE"; fi; done. (LC_ALL здесь нужен был, чтобы быть уверенным, что дата будет занимать именно три слова в выводе ls.) Мало того, что это решение выглядит некрасиво, оно ещё страдает рядом недостатков. Во-первых, оно не будет работать, если имя файла содержит перевод строки или начинается с пробела. Далее, нам нужно явно перечислить названия всех столбцов ls, ну или как минимум помнить, на каком месте находятся интересующие нас (т. е. SIZE и FILE). Если мы ошибёмся в порядке столбцов, то ошибка выяснится лишь на этапе выполнения. Когда мы удалим не те файлы. :) А как бы выглядело решение в идеальном мире, который я предлагаю? Как-то так: ls | grep 'size > 1kb' | rm. Кратко, а главное смысл виден из кода, и невозможно ошибиться. Смотрите. ls в моём мире всегда выдаёт всю инфу. Специальня опция -l для этого не нужна. Если нужно убрать все столбцы и оставить только имя файла, то это делается специальной утилитой, в которую нужно направить вывод ls. Итак, ls выдаёт список файлов. В некоем структуированном виде, скажем, JSON. Это представление «знает» названия столбцов и их типы, т. е. что это, строка, число или что-то ещё. Далее этот вывод направляется в grep, который в моём мире выбирает нужные строки из этого JSON. JSON «знает» названия полей, поэтому grep «понимает» size. Более того, JSON содержит инфу о типе поля size. Он содержит инфу о том, что это число, и даже что это не просто число, а размер файла. Поэтому можно сравнить его с 1kb. Далее grep направляет вывод в rm. rm «видит», что он получил файлы. Да, да, JSON ещё и хранит инфу о типе этих строк, о том, что это — файлы. И rm их удаляет. А ещё JSON отвечает за правильное экранирование специальных символов. Поэтому файлы со спецсимволами «просто работают». Круто? Идею я взял отсюда: www.opennet.ru/opennews/art.shtml?num=34591 (там ещё есть ссылка на более подробный английский оригинал), посмотрите. Ещё замечу, что в Windows Powershell реализовано как раз что-то похожее на эту идею.

  • UNIX shell. Ещё одна базовая идея UNIX. Причём о мелких недостатках UNIX shell я уже поговорил в первой части статьи. Сейчас будут крупные. В чём «крутость» UNIX shell? В том, что на момент своего появления (это было очень давно) UNIX shell был гораздо мощнее командных интерпретаторов, встроенных в другие ОС. И позволял писать более мощные скрипты. Да и вообще, на момент своего появления UNIX shell был, видимо, самым мощным из скриптовых языков вообще. Потому что нормальных скриптовых языков, т. е. таких, которые бы позволяли полноценное программирование, а не только скриптинг, тогда, видимо, вообще не существовало. Это потом уже в один прекрасный день один программист по имени Larry Wall заметил, что UNIX shell всё-таки недостаёт до нормального языка программирования. И он захотел соединить краткость UNIX shell'а с возможностью полноценного программирования из C. И создал Perl. Да, Perl и другие последующие скриптовые языки программирования фактически заменили UNIX shell. Это константирует даже Роб Пайк, один из авторов (как я считаю) той самой «философии UNIX» (про него мы ещё поговорим). Вот здесь: interviews.slashdot.org/story/04/10/18/1153211/rob-pike-responds на вопрос об «одной утилите для одной вещи» он сказал: «Those days are dead and gone and the eulogy was delivered by Perl». Причём я считаю, что эта его фраза относилась к типичному использованию UNIX shell, т. е. к ситуации связывания большого количества маленьких утилит в shell-скрипте. Нет, говорит Пайк, просто используйте Perl.

    Я не закончил про UNIX shell. Рассмотрим ещё раз пример кода на shell, который я уже приводил: find foo -print0 | while IFS="" read -rd "" A; do touch -- "$A"; done. Здесь в цикле вызывается touch (да, я знаю, что этот код можно переписать на xargs, причём так, чтобы touch вызывался только один раз; но давайте пока забьём на это, хорошо?). В цикле вызывается touch! То есть для каждого файла будет запущен новый процесс! Это нереально неэффективно. Код на любом другом языке программирования будет работать быстрее этого. Просто на момент появления UNIX shell он был одним из немногих языков, которые позволяют написать это действие в одну строчку.

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

  • Простота. Здесь я говорю не конкретно про shell и про связывание кучи простых утилит из shell'а (про это был предыдущий пункт), а про простоту вообще. Использование простых инструментов. Скажем, редактирование картинки sed'ом. Да, да. Конвертим jpg в ppm при помощи командной строки. Затем при помощи графического редактора, grep, sed и такой-то матери редактируем картинку. А потом обратно в jpg. Да, так можно. Но часто photoshop'ом или gimp'ом всё-таки лучше. Хоть это и большие, интегрированные программы. Не в стиле UNIX.

На этом я закончу эти пункты. Да, хватит. Есть идеи в UNIX, которые мне реально нравятся. Скажем, «программа должна делать одну вещь и делать её хорошо». Но не в контексте shell. Вы уже поняли, что я не люблю shell. (Ещё раз повторю, я считаю, что в приведённом выше интервью Пайка он воспринял принцип «программа должна делать одну вещь и делать её хорошо» именно в контексте shell и потому отверг его.) Нет, я говорю про этот принцип в своей сути. Скажем, консольный почтовый клиент не должен иметь встроенный текстовый редактор, он должен просто запустить некий внешний редактор. Или вот принцип, по которому нужно писать консольное ядро для программы и потом графическую оболочку для этого ядра.

Теперь общая картина. Однажды появился UNIX. На момент появления он был прорывом. И он был во многом лучше своих конкурентов. UNIX имел много идей. И, как и любая ОС, UNIX требовал от программистов соблюдения некоторых принципов для написания прикладных программ. Идеи, лежащие в основе UNIX, стали называться «философией UNIX». Одним из тех людей, которые сформулировали философию UNIX, был уже упомянутый Роб Пайк. Он это сделал в своей презентации «UNIX Style, or cat -v Considered Harmful» ( harmful.cat-v.org/cat-v ). После презентации он вместе с Керниганом опубликовал статью по мотивам презентации ( harmful.cat-v.org/cat-v/unix_prog_design.pdf ). В ней авторы рассказали о том, что, скажем, предназначение cat — это только конкатенация и ничего больше (ну то есть «склеивание» файлов, мы с вами помним, как расшифровывается cat, так ведь?). Возможно, что это Пайк как раз и придумал «философию UNIX». В честь этой презентации был назван сайт cat-v.org, почитайте его, очень интересный сайт.

Но потом, через много лет, этот же Пайк сделал ещё две презентации, в которых, как я считаю, отменил свою философию обратно. Поняли, фанатики, да? Ваш кумир отказался от своей же философии. Можете расходиться по домам. В первой презентации «Systems Software Research is Irrelevant» ( doc.cat-v.org/bell_labs/utah2000 ) Пайк сетует на то, что никто больше не пишет новых ОС. А даже если и пишут, то просто ещё один UNIX (который подразумевается в этой презентации уже чем-то неинтересным): «New operating systems today tend to be just ways of reimplementing Unix. If they have a novel architecture — and some do — the first thing to build is the Unix emulation layer. How can operating systems research be relevant when the resulting operating systems are all indistinguishable?»

Вторую презентацию ( doc.cat-v.org/bell_labs/good_bad_ugly ) Пайк прямо называет: «The Good, the Bad, and the Ugly: The Unix Legacy». Пайк говорит, что простой текст не универсален, он хорош, но работает не всегда: «What makes the system good at what it's good at is also what makes it bad at what it's bad at. Its strengths are also its weaknesses. A simple example: flat text files. Amazing expressive power, huge convenience, but serious problems in pushing past a prototype level of performance or packaging. Compare the famous spell pipeline with an interactive spell-checker». Далее: «C hasn't changed much since the 1970s… And — let's face it — it's ugly». Дальше Пайк признаёт ограниченность пайпов, соединяющих простые утилиты, ограниченность регексов.

UNIX был гениальным на момент своего появления. Особенно, если учесть, какие инструменты были в распоряжении у авторов UNIX. У них не было уже готового UNIX, чтобы на нём можно было разрабатывать UNIX. У них не было IDE. И программировали они вообще на ассемблере изначально. У них, видимо, был только ассемблер и текстовый редактор.

Люди, стоящие у истоков UNIX, в определённый момент начали писать новую ОС: Plan 9. В том числе упомянутые Томпсон, Ритчи и Пайк. Учитывая многие ошибки UNIX. Но и Plan 9 никто не возводит в абсолют. В «Systems Software Research is Irrelevant» Пайк упоминает Plan 9, но несмотря на это всё равно призывает писать новые ОС.

James Hague, ветеран программирования (занимается программированием с восьмидесятых) пишет: «What I was trying to get across is that if you romanticize Unix, if you view it as a thing of perfection, then you lose your ability to imagine better alternatives and become blind to potentially dramatic shifts in thinking» ( prog21.dadgum.com/128.html ). Прочитайте эту статью и его же статью «Free Your Technical Aesthetic from the 1970s» ( prog21.dadgum.com/74.html ), на которую он ссылается. (Вообще, если вам понравилась моя статья, то и его блог тоже, наверное, понравится, погуляйте там по ссылкам.)

Итак, я не хочу сказать, что UNIX — плохая система. Просто обращаю ваше внимание на то, что у неё есть полно недостатков, как и у других систем. И «философию UNIX» я не отменяю, просто обращаю внимание, что она не абсолют. Мой текст обращён скорее к фанатикам UNIX и GNU/Linux. Провокационный тон просто чтобы привлечь ваше внимание.

Автор: safinaskar

Источник

Поделиться

* - обязательные к заполнению поля