Пользователь
0,0
рейтинг
8 декабря 2012 в 14:56

Администрирование → Inferno Shell

FAQ: Что такое OS Inferno и зачем она нужна?

Оболочка ОС Инферно много лет вызывала у меня исключительно отрицательные эмоции. И я никогда не понимал, что в Inferno sh вызывает восторг у некоторых людей. Но, как говорится, лучше поздно чем никогда — сегодня я решил таки тщательно разобраться с шеллом, и в результате меня тоже проняло — это таки действительно уникальная вещь! Невероятно элегантная и простая.

Начну всё-таки с недостатков, для большей объективности. Главный — шелл очень медленный. Неизвестно почему, но все шеллы инферно (их вообще-то несколько, но сейчас речь про /dis/sh.dis) очень медленные. Это крайне странно, т.к. обычно скорость работы приложений написанных на Limbo (в JIT-режиме) находится между скоростью C и быстрых скриптовых языков вроде Perl. Поэтому полноценные приложения на sh писать не получится. Но, тем не менее, на каждый чих расчехлять Limbo тоже неудобно, так что всяческие стартовые скрипты и прочую мелочёвку всё-равно приходится писать на sh. Второй серьёзный недостаток — неудобство использования в текстовой консоли, отсутствие истории команд, автодополнения, удобного редактирования при вводе сильно раздражает (но мне только что рассказали про утилиту rlwrap, запуск emu через rlwrap -a, похоже, способен решить эту проблему). Третий — синтаксис этого шелла необычен использованием непарных кавычек, что при попытке подсветить синтаксис его скриптов используя подсветку для абсолютно любого другого шелла (в связи с отсутствием готовой подсветки для инферновского) приводит к полному кошмару. Эту проблему я собирался решить сегодня, реализовав подсветку синтаксиса для vim, для чего и сел разбираться с шеллом… а в результате вместо подсветки синтаксиса не удержался, и пишу эту статью. :)

Содержание




Из чего же, из чего же, из чего же сделаны наши скрипты?


Функциональность, поддерживаемая /dis/sh.dis «из коробки» впечатляет! Нет даже условных операторов и циклов! Нет функций. А что тогда есть, и как в этих условиях можно существовать? Сейчас увидите. Итак, что есть:
  • запуск приложений (включая конвейеры и перенаправление ввода/вывода)
  • строки, списки строк, блоки команд
  • переменные окружения
  • шаблоны имён файлов (*, ?, […])
  • и немного встроенных команд и команд для работы со строками
Наверное, увидев последний пункт вы подумали «Ага! Какие-то специальные команды. Наверняка всё остальное делают они, всё банально.»… но нет, вы не угадали. Из встроенных команд используются обычно три: exit, run и load; а часто используемые команды для работы со строками это quote и unquote. Что касается run, то она просто выполняет указанный скрипт в текущем шелле (аналог . или source в bash).

А вот load — да, это бомба! Она позволяет подгружать в текущий шелл дополнительные модули, которые пишутся на Limbo и позволяют добавить к шеллу абсолютно любую функциональность — if, for, функции, исключения, регулярные выражения, математические операции, и т.д. и т.п. Ещё есть команда unload, позволяющая динамически эти модули выгружать. :) Тем не менее, даже без подгружаемых модулей шелл абсолютно полноценен и функционален — что я и докажу в конце статьи реализовав на «голом sh» if и for!

Ой, кое что ещё я забыл упомянуть. (Думаете, теперь-то точно «ага!»? Не-а.) Комментарии он ещё поддерживает. Начинающиеся с #. :)

Запуск приложений


Многое идентично любому *nix шеллу:
  • запускает приложения (.dis файлы) и скрипты (файлы, в которых первая строка это shebang #!)
  • команды разделяются ;
  • запуск команд в фоне через &
  • конвейеры команд через |
  • перенаправления stdin/stdout через >, >> и <

; echo one two | wc
    1       2       8

Но есть и отличия.

Дополнительные перенаправления ввода-вывода команд

  • указание файлового дескриптора (для перенаправления stderr или открытия дополнительных дескрипторов плюс к 0, 1 и 2 доступным по умолчанию)
    • cmd <stdin.txt >stdout.txt >[2]stderr.txt
    • cmd >[1=2] (перенаправление stdin в stderr)
    • cmd <[3]file.txt (команда запускается с дополнительным файловым дескриптором 3 открытым на чтение из файла)
    • cmda |[2] cmdb (вместо stdout первой команды на вход конвейера отправляется её stderr, а stdout выводится просто на экран)
    • cmda |[1=2] cmdb (в конвейер снова отправляется stderr, а не stdout cmda, но подключается он не к stdin, а к stdout cmdb — который открывается на чтение, а не запись, разумеется)

  • открытие одновременно на чтение и запись
    • cmd <>in_pipe <>[1]out_pipe (stdin и stdout открыты одновременно на чтение и запись в разные файлы)

  • нелинейные конвейеры
    • cmd <{первый;блок;команд} >{второй;блок;команд} (оба блока команд запускаются параллельно с cmd, при этом cmd получает два параметра — имена файлов а-ля /fd/номер, которые подключены через pipe к stdout первого блока команд и stdin второго блока команд)

Последняя фича особо интересна. Например, стандартная команда сравнения двух файлов cmp с её помощью может сравнить не файлы, а результаты работы двух других команд: cmp <{ ls /dir1 } <{ ls /dir2 }.

$status

В инферно необычный подход к реализации кода завершения (exit status) приложения. В традиционных ОС любая программа завершается числом: 0 если всё в порядке, и любым другим при ошибке. В инферно любая программа либо просто завершается (если всё в порядке), либо генерирует исключение, значение которого это строка с текстом ошибки.

Принято соглашение, по которому эта строка должна начинаться на «fail:» если это ошибка штатная — т.е. приложение не «упало», а просто хочет выйти вернув эту ошибку тому, кто это приложение запустил (обычно это шелл). Eсли ошибка не начинается на «fail:», то после завершения приложения его процесс останется в памяти (в состоянии «broken»), чтобы его можно было исследовать отладчиками и выяснить, почему возникло это исключение (аналог core dump-ов, только вместо сохранения на диск они висят в памяти).

Так вот, после завершения запущенной команды её «код выхода» — т.е. текст ошибки-исключения будет находится в переменной окружения $status (префикс «fail:» из него будет автоматически удалён, если текст исключения кроме «fail:» больше ничего не содержал, то в $status будет строка «failed»).

Строки, списки строк, блоки команд


Инферновский шелл оперирует только строками и списками строк.

Строка

Строка это либо слово не содержащее пробелов и некоторых спец-символов, либо любые символы заключённые в одинарные кавычки. Единственный «экранируемый» символ в такой строке — сама одинарная кавычка, и записывается он двумя такими кавычками:
; echo 'quote   ''   <-- here'
quote   '   <-- here

Никаких \n, \t, интерполяции переменных и т.п. в строках не поддерживается. Если в строку нужно вставить символ перевода строки — либо просто вставляете его as is (нажав Enter), либо конкатенируете строку с переменной, содержащей этот символ. В инферно есть утилитка unicode(1) которая умеет выводить любой символ по его коду, вот с её помощью это и делается (конструкция "{команда} описана ниже, а пока считайте это аналогом `команда` bash-а):
; cr="{unicode -t 0D}
; lf="{unicode -t 0A}
; tab="{unicode -t 09}
; echo -n 'a'$cr$lf'b'$tab'c' | xd -1x
0000000 61 0d 0a 62 09 63
0000006

Двойные кавычки для строк не используются (кстати, парные двойные кавычки в инферновском шелле не используются ни для чего вообще).

Блок команд

Блок команд записывается внутри фигурных скобок, и весь блок обрабатывается шеллом как одна строка (включающая символы фигурных скобок).
; { echo one; echo two }
one
two
; '{ echo one; echo two }'
one
two

Единственное отличие от строки в одинарных кавычках заключается в том, что sh проверяет синтаксис блоков команд и переформатирует их в более компактные строки.
; echo { cmd | }
sh: stdin: parse error: syntax error
; echo { cmd | cmd }
{cmd|cmd}
; echo {
echo     one
echo two
}
{echo one;echo two}


Список строк

Списки строк это просто ноль и более строк разделённых пробельными символами. Их можно заключать в круглые скобки, но в абсолютном большинстве случаев это не обязательно.

Любая команда шелла это просто список строк, где первая строка содержит команду либо блок кода, а остальные строки параметры для них.
; echo one two three
one two three
; echo (one two) (three)
one two three
; (echo () (one (two three)))
one two three
; ({echo Hello, $1!} World)
Hello, World!

Для конкатенации списков строк используется оператор ^. Его можно применять двум спискам содержащим либо одинаковое количество элементов (тогда элементы конкатенируются попарно), либо один из списков должен содержать только один элемент, тогда он будет конкатенирован с каждым элементом второго списка. Как частный случай, если оба списка содержат только по одному элементу — получается обычная конкатенация двух строк.
; echo (a b c) ^ (1 2 3)
a1 b2 c3
; echo (a b) ^ 1
a1 b1
; echo 1 ^ (a b)
1a 1b
; echo a ^ b
ab

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

Переменные окружения


Работа с переменными в инферновском шелле выглядит обманчиво похоже на традиционные шеллы, но не дайте себя этим обмануть!
; a = 'World'
; echo Hello, $a!
Hello, World!


/env

Переменные окружения в ОС Инферно реализованы иначе, чем в традиционных ОС. Вместо введения дополнительного POSIX API для работы с переменными окружения и передачи их каждому процессу через кошмар вроде int execvpe(const char *file, char *const argv[], char *const envp[]); переменные окружения это просто файлы в каталоге /env. Это даёт любопытные возможности, например приложение может предоставить доступ к своим переменным окружения другим приложениями по сети — просто экспортировав (через listen(1) и export(4)) свой /env.

Можно создавать/удалять/изменять переменные окружения создавая, удаляя и изменяя файлы в /env. Но стоит учитывать, что текущий sh держит в памяти копию всех переменных окружения, поэтому изменения сделанные через файлы в /env увидят запускаемые приложения, но не текущий sh. С другой стороны, изменяя переменные обычным для шелла способом (через оператор =) вы автоматически обновляете файлы в /env.

Имена переменных шелла могут содержать любые символы, в т.ч. такие, которые недопустимы для имён файлов (например имена ., .. и содержащие /). Переменные с такими именами будут доступны только в текущем sh, и запускаемые приложения их не увидят.

Списки

Ещё одно важное отличие — все переменные содержат только списки строк. Вы не можете положить в переменную одну строку — это всегда будет список из одной строки.

Сохранение в переменную пустого списка (явно через присваивание a=() или неявно через a=) — это удаление переменной.
  • $var это получение списка строк из переменной var, и в ней этих строк может быть ноль, одна или несколько
  • $#var это получение количества элементов в списке строк $var
  • $"var это конкатенация всех элементов списка строк из $var в одну строку (через пробел)

; var = 'first   str'  second
; echo $var
first   str second
; echo $#var
2
; echo $"var
first   str second

Как видите, результат вывода $var и $"var визуально не отличается, т.к. echo выводит все свои параметры через пробел, и $" тоже объединяет все элементы переменной через пробел. Но первая команда echo получила два параметра, а последняя — один.

Всегда, когда вы как-бы неявно конкатенируете значение переменной с чем либо — шелл вставляет оператор конкатенации ^ (который, как вы помните, работает со списками строк, а не строками). Это удобно, хотя с непривычки может оказаться неожиданным:
; flags = a b c
; files = file1 file2
; echo -$flags $files.b
-a -b -c file1.b file2.b
; echo { echo -$flags $files.b }
{echo -^$flags $files^.b}

Присваивание в список переменных тоже работает. Если в правой части список из большего количества элементов, чем переменных в левой части — последняя переменная получит остаток списка, а в предыдущих переменных будет в каждой по одному элементу.
; list = a b c d
; (head tail) = $list
; echo $head
a
; echo $tail
b c d
; (x y) = (3 5)
; (x y) = ($y $x)
; echo 'x='^$x 'y='^$y
x=5 y=3

Список всех параметров скрипта или любого блока кода находится в переменной $*, плюс отдельные параметры находятся в переменных $1, $2, ….

Области видимости

Каждый блок кода образует свою область видимости. Значения переменным можно присваивать операторами = и :=. Первый изменит значение существующей переменной либо создаст новую переменную в текущей области видимости. Второй всегда создаёт новую переменную, перекрывая старое значение переменной с этим именем (если она существовала) до конца текущего блока.
; a = 1
; { a = 2 ; echo $a }
2
; echo $a
2
; { a := 3; echo $a }
3
; echo $a
2
; { b := 4; echo $b }
4
; echo b is $b
b is


Ссылки на переменные

Имя переменной — это просто строка после символа $. И она может быть любой строкой — в одинарных кавычках или другой переменной.
; 'var' = 10
; echo $var
10
; ref = 'var'
; echo $$ref
10
; echo $'var'
10
; echo $('va' ^ r)
10


Перехватываем вывод команд


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

Встречайте те самые непарные кавычки, которые вечно ломают подсветку синтаксиса.
  • `{команда} (выполняет команду, и возвращает то, что она вывела на stdout в виде списка строк; элементы списка разделяются используя значение переменной $ifs; если она не определена, то считается, что в ней строка из трёх символов: пробел, табуляция и перевод строки)
  • "{команда} (выполняет команду, и возвращает то, что она вывела на stdout в виде одной строки — конкатенации всех строк выведенных командой)
В качестве примера загрузим список файлов через вызов ls. Учитывая, что имена файлов могут содержать пробелы, стандартное поведение `{} нам не подойдёт — нам необходимо разделять элементы списка только по переводу строки.
; ifs := "{unicode -t 0A}
; files := `{ ls / }
; echo $#files
82
; ls / | wc -l
	82

Но вообще-то это делается проще и надёжнее через шаблоны имён файлов, которые разворачиваются шеллом в список имён файлов:
; files2 := /*
; echo $#files2
82


Встроенные команды


Встроенные команды бывают двух типов: обычные и для работы со строками.

Обычные команды вызываются так же, как запускаются приложения/скрипты:
; run /lib/sh/profile
; load std
; unload std
; exit

Команды для работы со строками вызываются через ${команда параметры} и возвращают (не выводят на stdout, а именно возвращают — как это делает например обращение к значению переменной) обычную строку — т.е. их нужно использовать в параметрах обычных команд или в правой части присваивания значений в переменные. Например, команда ${quote} экранирует переданный ей список строк в одну строку, а ${unquote} выполняет обратную операцию превращая одну строку в список строк.
; list = 'a  b'  c d
; echo $list
a  b c d
; echo ${quote $list}
'a  b' c d
; echo ${unquote ${quote $list}}
a  b c d


Добавляем if, for, функции


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

Всё реализуется используя исключительно:
  • переменные
  • строки и списки строк
  • блоки кода и их параметры


Делаем свои «функции»

Как я уже упоминал, любая команда шелла это просто список строк, где первая строка это команда, а остальные строки её параметры. А блок команд шелла это просто строка, и её можно сохранить в переменной. И параметры любой блок команд получает в $*, $1, etc.
; hello = { echo Hello, $1! }
; $hello World
Hello, World!

Более того, мы даже можем сделать каррирование функций в лучшем духе функционального программирования. :)
; greet = {echo $1, $2!}
; hi    = $greet Hi
; hello = $greet Hello
; $hi World
Hi, World!
; $hello World
Hello, World!

Ещё один пример — можно использовать параметры блока чтобы получить нужный элемент списка по номеру:
; list = a b c d e f
; { echo $3 } $list
c


Делаем свой for

Полноценный удобный if я делать не пытался, мне было интересно реализовать for, а if я сделал минимально функциональный т.к. он был необходим для for (цикл ведь надо когда-то остановить, и без if это сделать проблематично).
; do = { $* }
; dont = { }
; if = {
    (cmd cond) := $*
    { $2 $cmd } $cond $do $dont
}
; for = {
    (var in list) := $*
    code := $$#*
    $iter $var $code $list
}
; iter = { 
    (var code list) := $*
    (cur list) := $list
    (next unused) := $list
    $if {$var=$cur; $code; $iter $var $code $list} $next
}

; $for i in 10 20 30 { echo i is $i }
i is 10
i is 20
i is 30
;


Интересные мелочи


По умолчанию шелл при запуске скрипта форкает namespace, таким образом скрипт не может изменить namespace процесса, который его запустил. Это не подходит скриптам, чья задача как раз настройка namespace своего родителя. Такие скрипты должны начинаться на #!/dis/sh -n.

Встроенная команда loaded выводит список всех встроенных команд из всех загруженных модулей. А встроенная команда whatis выводит информацию по переменным, функциям, командам, etc.

Если создать переменную $autoload со списком модулей шелла, то эти модули будут автоматически загружаться в каждый запускаемый шелл.

В шелле есть поддержка синтаксического сахара: && и ||. Эти операторы доступны в «голом sh», но преобразуются в вызов встроенных команд and и or, которых в «голом sh» нет, они из модуля std (так что использовать && и || можно только после load std).
; echo { cmd && cmd }
{and {cmd} {cmd}}
; echo { cmd || cmd }
{or {cmd} {cmd}}

Приложения написанные на Limbo получив, например, при запуске параметром командной строки строку с блоком команд sh могут очень просто её выполнить используя модуль sh(2).

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

Резюме


Эта небольшая статья — практически полный reference guide по инферновскому шеллу. В смысле, описана вся функциональность базового шелла, и довольно подробно — со всеми нюансами и примерами. Если вы прочитаете sh(1), то увидите, что я не упомянул разве что переменные $apid, $prompt, пару-тройку встроенных команд, опции самого sh да полный список спец.символов, которые нельзя использовать в строках вне одинарных кавычек.

Если не брать довольно продвинутые возможности по перенаправлению ввода/вывода, то используя всего лишь:
  • строки с тривиальнейшими правилами экранирования
  • блоки команд (а по сути это просто те же строки)
  • списки строк с одним оператором ^
  • переменные с одним оператором =
  • обращение к переменным через $var, $#var и $"var
реализован вполне полноценный шелл! Полноценный даже в «голом» виде, что убедительно доказано возможностью реализовать на нём функции, if и for (и ещё я придумал, как сделать аналог команды raise из модуля std — т.е. аналог традиционной /bin/false :) но это хак через run и в статью я его включать не стал).

А когда мы к нему начинаем подгружать модули, возможности и удобство использования шелла повышается на порядок. К примеру, тот же модуль sh-std(1) добавляет:
  • несколько условных операторов (and, or, if)
  • команды для сравнения и проверки условий (!, ~, no)
  • несколько операторов цикла (apply, for, while, getlines)
  • функции обоих видов (fn, subfn)
  • работу с исключениями и статусом (raise, rescue, status)
  • работу со строками и списками (${hd}, ${tl}, ${index}, ${split}, ${join})
  • etc.
Но все эти дополнительные команды никак не усложняют синтаксис самого шелла, он остаётся таким же тривиальным и элегантным!
Alex Efros @powerman
карма
302,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (30)

  • –17
    Photoshop под неё есть?
  • +4
    Разрыв мозга при первом прочтении… оставлю на осмысливание, через пару часов попробую еще раз понять…
  • +3
    В инферно любая программа либо просто завершается (если всё в порядке), либо генерирует исключение, значение которого это строка с текстом ошибки. Принято соглашение, по которому эта строка должна начинаться на «fail:» если это ошибка штатная — т.е. приложение не «упало», а просто хочет выйти вернув эту ошибку тому, кто это приложение запустил (обычно это шелл). [...] после завершения запущенной команды [...] текст ошибки-исключения будет находится в переменной окружения $status (префикс «fail:» из него будет автоматически удалён, если текст исключения кроме «fail:» больше ничего не содержал, то в $status будет строка «failed»).

    Вот за такое того, кто это проектировал, хочется убить.
    • +1
      Да, меня тоже всегда удивляло, нафига было вводить неоднозначность на пустом месте. Я даже больше скажу, дополнение насчёт «failed» появилось 4 года назад, после того, как я отрапортовал баг, что приложение упавшее с ошибкой «fail:» считается успешно завершившимся. Ну, значит и на старуху бывает проруха. Архитектура инферно содержит очень много практически гениальных решений, которые позволили невероятно упростить и саму ОС, и софт который под неё пишется (поэтому я и предпочитаю писать именно под инферно). Но с этим моментом они явно погорячились. К счастью, это всё-таки не критичная мелочь, и жить не мешает.
      • 0
        Вот Вы говорите что пишите под инферно — можете отписать какие именно приложения? Меня интересует именно сетевой код + работа в нативном режиме.
        • 0
          Если под нативным режимом имеется в виду запуск инферно на голом железе, то я этого не делал — я использую инферно под линухом просто как комфортный runtime для своего кода, виртуальную машину а-ля JVM, только намного проще и функциональнее (всё-таки это полноценная ОС, в неё можно удалённо зайти, позапускать/покилять процессы, подключиться удалённым отладчиком, etc.).

          Пишу в основном сетевые сервисы — обычные TCP сервера/клиенты, работающие в кластере (находящие друг друга через инферновский штатный реестр сервисов), общающиеся между собой в JSON, etc. Но по большей части такой стиль вызван тем, что инферновские сервисы добавляются в существующую систему, где остальные сервисы написаны в основном на Perl — если бы инферно внедрялась с самого начала, то вместо TCP использовалили бы более естественный для инферно протокол 9P/Styx и сервисы бы реализовывались не как TCP-сервера, а как обычные инферновские файловые сервера.
          • 0
            меня больше интересует возможность запуска Inferno как очень легкого гипервизора под другой Inferno. Поскольку сейчас в качестве хоста лучше всего использовать OpenBSD — и хоть она и хороша — но хочется native self-hosted.
            По поводу чисто 9P/Styx тут думаю не все так просто — т.к. если приложение работает через WAN то есть вероятность того что трафик могут «завернуть», поэтому использование TCP считаю оправданным, а Styx хорош только для локальных задач.
            P.S. в личку отписал пару вопросов про итоговый КПД.
            • 0
              Как это «завернуть»? Сам 9P работает обычно через TCP (порт 6666), с чего бы его кто-то стал блокировать? У 9P другая проблема — высокая latency, для задач вроде раздачи больших файлов не подойдёт. А вот как раз для виртуальных файловых серверов, которые на самом деле не файлы, а какие-то сервисы просто выглядящие как каталог с файлами — 9P идеален.
              • 0
                если у Вас стоит в ДЦ железка с умной фильтрацией трафика и балансирование — то к сожалению про 9P она не знает, соответственно поэтому и возможны проблемы.
                про latency это Я знаю — т.к. было опыт с pNFS.
      • 0
        и очень плохо что 2 мои любимые ОС — QSS QNX и Vita Nuova Inferno — обе не поддерживают 64бита — а если учесть что в продакшене сейчас только x64 — то наводит на грусные мысли.
        • 0
          Это, конечно, пичалька… но в 32-битном режиме инферно отлично работает на 64-битных ОС. Самой инферно 64 бита внутри особо не нужны. Так что хочется, конечно, нативную 64-битную версию, но её отсутствие сильно жить не мешает. Вроде несколько лет назад в maillist-е пробегал патч добавляющий поддержку 64-бит, но он Форсайту не понравился чем-то — по крайней мере ходят такие слухи.
          • 0
            в режиме эмуляции (OS-hosted) проблем конечно нету, однако если делать Out-of-box решение — то нужен доступ к памяти более чем 4GB по сумме — к сожалению попытка написать приложения с возможностью аллоцирования такого региона не удалась — тестил на RHEL6 c 16GB RAM, но запустить 8 приложения по 1GB в каждом не удалось :(
    • 0
      «извините, у вас абстракция протекла»
      • 0
        Ну, на протёкшую абстракцию эта проблема не тянет. Это просто пара строк в коде sh, которые, по-моему, надо просто выкинуть. Я даже не уверен, что от этого что-то где-то сломается.
        • 0
          протёкшая абстракция — это
          генерирует исключение, значение которого это строка с текстом ошибки
          А
          соглашение, по которому эта строка должна начинаться на «fail:» если это ошибка штатная
          — типичный workaround.
          • 0
            Почему? Что не так с исключениями? В инферно архитектура построена так, чтобы все задачи решать минимумом выделенных сущностей. Если в приложениях уже есть исключения (и в 99% это обычные строки, а не объекты), зачем вводить отдельную сущность «код возврата приложения» если можно использовать для этой цели те же исключения? Кроме того, в инферно нет разницы между приложениями и модулями/библиотеками, так что ваше приложение абсолютно не обязательно было вызвано именно как приложение, в отдельной нити и т.п. — его функцию init() могли запустить и как обычную функцию из другого приложения, так что завершение вашего приложения с ошибкой через генерирование исключения позволит штатно перехватить и обработать это исключение в программе, которая вызвала ваш init().

            Что касается workaround-а, то это не workaround а соглашение. Не всегда нужно писать код реализующий некую функциональность, иногда достаточно задокументировать определённые соглашения, которые позволят получить эту же функциональность без лишних строчек кода. В инферно такие соглашения используются довольно активно, и это опять же позволило много упростить.
            • 0
              завершение вашего приложения с ошибкой через генерирование исключения позволит штатно перехватить и обработать это исключение в программе, которая вызвала ваш init().

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

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

              Я надеюсь, к этим соглашениям верификатор прилагается?

              (ключевые слова: programming by contract, defensive programming)
              • 0
                Насколько я понимаю, исключения в инферно изначально были только строковые, возможность ограниченно использовать объекты была добавлена значительно позднее, и ей практически никто не пользуется (по-моему, её используют только в сложных парсерах). Строки-исключения при выборе обработчика обычно парсятся самим Limbo по шаблону «prefix*», что просто, наглядно, и никаких проблем не вызывает.

                Верификатор не прилагается. Желающие выстрелить себе в ногу всегда найдут способ это сделать, сколько assert-ов в код не пихай.
  • +2
    Насколько красив и строен Unix Shell, настолько странен и неуклюж Windows Shell, многие программы до сих пор не научились верно возвращать return code.
    • –1
      А вы про какой именно Windows Shell говорите?
    • –1
      Вроде как Windows Shell это explorer.exe (штатный shell по умолчанию — как оболочка), а вот командная строка с Windows Vista — это Windows PowerShell — и там возможности весьма и весьма широки (тесная связь с WMI уже хорошо)
      • 0
        упс — строку @ + shell определили как юзера. Сорри.
      • +1
        И в Vista и в W7 по умолчанию используется cmd. PowerShell уже как отдельный необязательный компонент.
        P.S. PowerShell прекрасен, но эмулятор терминала просто ужасен :(
  • 0
    Смайлики. Теперь и в топиках.
    • 0
      Действительно. Какой ужас. Мы же тут все серьёзные люди, как можно…
      • 0
        Видите, у вас начинает получается и без них.
  • 0
    Кстати говоря, alias emu-g='rlwrap -a -r emu-g' действительно делает работу с шеллом инферно намного удобнее! И подсветку синтаксиса для Vim я уже сделал. Так что из недостатков осталась только скорость работы. Впрочем, насчёт неё тоже есть идея что проверить, но быстро это сделать не получится.
  • 0
    Я когда-то пытался перейти с bash на ремейк rc из Plain9. Там была даже некоторая поддержка readline, но комплетишен был гараздо хуже.
    Через rlwrap копмлетишен нормально не сделаешь. А жаль, shell был бы удобный.
    • 0
      Почему не сделаешь? Я не пробовал, но судя по доке через --filter можно реализовать всё, что нужно.
      • 0
        Комплетишен должен иметь доступ к внутреннему состоянию оболочки. К текущей директории, переменным среды. Отслеживать все это снаружи тяжело.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.