UNIX-подібні системи містять купу милиць. Крах «філософії UNIX»

Модератори, при наступному внеснии мною правки я просто внести правку в майстер-копію, розміщену на моєму комп'ютері і пересу її сюди. Так що всі ваші правки зникнуть. Якщо хочете повідомте мені про помилку, використовуйте стандартний спосіб (лічку). См. також: 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 from another 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 ласкавою 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 was the solution 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 | read A while, 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 A
        IFS="" 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 '%q\n' "$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 '%s\n' "$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:\windows\system32\drivers\etc\hosts
    )


«Філософія 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 похвала 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 is also at 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. Провокаційний тон щоб привернути вашу увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.