О блоге

Все новые материалы размещаются на Блогосайте alv.me. Старые - в процессе переноса.

05.09.2008

Zsh: мой любимый шелл

2001-2002 г

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

Содержание

Представление главного героя

Как можно догадаться из названия заметки, главным героем ее является командная оболочка Z-Shell или, сокращенно, zsh, о которой на протяжении всего предшествующего повествования не было сказано ни слова. Настало время исправить это упущение.

Итак, zsh - оболочка из клана sh-совместимых, первоначально разрабатывавшаяся Паулем Фальстадом (Paul Falstad), ныне развивается в рамках самостоятельного проекта сообществом энтузиастов (Zsh Development Group) при координации Петера Стефенсона (Peter Stephenson). В отличие от bash, прямого (как, впрочем, и косвенного) отношения к GNU zsh не имеет, распространяется под собственной лицензией BSD-стиля, а, следовательно, является полностью свободной программой.

Существует мнение (и не только мое), что в zsh нашли свое воплощение все прогрессивные тенденции таких развитых оболочек, как bash и tcsh. И, ознакомившись с его возможностями, с этим трудно не согласиться - в zsh есть все, что было хорошего в тех обеих оболочках, но, если так можно выразиться, в превосходной степени.

Действительно, какими особенностями определяется в первую очередь удобство интерактивной работы в командной оболочке? В порядке, котором с ними сталкивается пользователь, это будут:

  • автодополнение командной строки;
  • возможности навигации по ней и ее редактирования;
  • просмотр буфера истории команд;
  • возможность минимизации ввода за счет использования псевдонимов.

Автодополнением клавишей Tab команд, частично введенных в ответ на приглашение оболочки, трудно удивить пользователей bash или tcsh. Столь же естественно, что при возможности безальтернативного дополнения именно оно и происходит, а при наличии некоторых альтернатив выводятся возможные варианты. Однако zsh идет дальше - и после вывода таковых в ответ на последующие нажатия клавиши табулятора начинает автоматический их перебор.

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

$ man zsha

и нажать табулятор, чтобы развернуть его до полного

$ man zshall

Более того, автодополнению (и автоматическому перебору его возможностей) подвержены даже опции многих команд. Это особенно показательно для таких синтаксически сложных команд, как find. Так, последовательность

$ find / -na

будет автоматически дополнена до

$ find / -name

А после указания этого для указания опции действия можно ограничиться вводом символа дефиса

$ find / -name filename -

и выбрать необходимое действие из предложенного списка, Например, print - вывод на экран, или exec - исполнение сторонней команды.

И еще один частный случай автодополнения уникален для zsh: развертывание путей при сокращенном их наборе. Так, что просмотреть содержимое каталога /usr/portage/distfiles, достаточно набрать в командной строке

$ ls /u/p/di

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

Автодополнение в zsh гармонично сочетается с автокоррекцией (т.н. spelling командной строки). Конечно, и это само по себе не уникально. Однако проверка правильности ввода и автокоррекция в zsh распространяются не только на встроенные (как в bash) и даже внешние (как в tcsh) команды, но даже на пути и аргументы. Причем если автокоррекция становится назойливой (например, для команд типа cp или mv она порывается исправить аргументы на имена существующих файлов), ее можно отключить - и именно только для определенных команд.

Средства навигации по командной строке и ее автоматического редактирования - необходимое условие комфорта в интерактивной работе внутри оболочки. Здесь говорить, казалось бы, не о чем - управляющие клавишные последовательности для таких действий давно уже вошли в обиход всех командных оболочек, претендующих на развитость. Однако и в этой области zsh есть чем похвастаться - в нем задействованы все комбинации клавиш для перемещения и удаления (как посимвольного, так и командными "словами" и фрагментами строки), которые существуют в bash и tcsh, причем - построенные по тем же принципам.

Управляющие последовательности в zsh построены по принципу сочетания клавиши Control+литера или Meta+литера, причем вторая комбинация обычно выступает в качестве "усиленного" варианта первой. Так, если Control+D удаляет символ в позиции курсора, то сочетание Meta+D проделывает это для всех символов от позиции курсора до конца командного слова.

Предусмотрены в zsh и клавишные комбинации для таких действий, как преобразование регистра литерных символов, "перетасовки" символов и командных "слов" в строке, заключения строки в кавычки (а при необходимости - и экранирования оных символами обратного слэша). Есть, конечно же, и комбинация для многоуровневой отмены ввода.

Действие большинства простых (двухклавишных) последовательностей дублируется "сложными", вида Meta+литера-Control+литера, которые прекрасно работают и при переключении на кириллическую раскладку клавиатуры.

Легко заметить, что управляющие последовательности в zsh реализованы с стиле emacs. Однако это - лишь один из возможных режимов, тот, который принят по умолчанию. При желании ничего не стоит переключить навигацию и управление в режим vi, если таковой кажется более привычным для пользователя.

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

И, наконец, такое мощное средство минимизации пользовательского ввода, как псевдонимы команд (aliases). Разумеется, в zsh (как и в bash или tcsh) псевдоним может быть присвоен любой команде со сколь угодно длинным набором опций. Так, куда как проще раз и навсегда определить команду ls как псевдоним самой же себя, но с опциями -FG, нежели каждый раз вспоминать, как отличить в ее выводе каталоги от обычных файлов.

Однако zsh идет дальше: в нем это дополняется возможностью определения псевдонимов для командных конвейеров в форме опции -g (от global aliases - именно так именуется эта возможность). Так, всем известно, что для обеспечения постраничного вывода любой команды (например, той же ls) вывод этот нужно передать по конвейеру (pipe) программе-pager'у (less или more). Однако не лениво ли - каждый раз вводить что-нибудь вроде

$ ls | less

да еще и не забывать это делать? Если лениво - на помощь придут глобальные псевдонимы. Опять же раз и навсегда определяем, что опция -g со значением L есть псевдоним для конвейера '| less':

$ alias -g L='| less'

после чего имеем возможность, указывая ее после команды, требующей постраничного ввода, именно его и получать:

$ ls -g L

Казалось бы, не намного проще? Ан нет: ведь ничто не препятствует нам создать еще один, обычный, псевдоним для команды ls с этой опцией, например:

$ alias lp='ls -g L'

чтобы при необходимости постраничного вывода списка каталога именно к нему и прибегнуть.

Важный момент облегчения существования пользователя в любом шелле - вид приглашения командной строки, должная настройка которого может часто избавить от лишнего набора команд (уж от команды pwd я по возможности стараюсь избавиться именно таким образом). Так вот, zsh поддерживает несколько независимо настраиваемых обычных приглашений - обычное, или первичное, вторичное - для многострочных команд, "выделенное", приглашение при выводе вариантов автокоррекции и даже специальное "приглашение" в правой части командной строки. Которое, конечно, собственно приглашением не является, но позволяет вывести полезную информацию, например, текущее время или дату, номер виртуальной консоли, и т.д. Для пущей экспрессии настраивается также передача символов в любом приглашении - цветом ли, выделением, инверсией, подчеркиванием.

Очень полезная возможность - различение вида приглашения для обычного пользователя, получившего права root'а в результате команды su без опций (то есть без перечитывания профильных файлов администратора), от собственно пользовательского приглашения - дабы не забывал юзер о временности своих полномочиях.

Надеюсь, что мне удалось убедить читателя в превосходных интерактивных возможностях оболочки zsh. Теперь стоит поговорить о функциональности, которая проявляется не только в интерактивной работе (но и, скажем, при сочинении скриптов). Функциональность же любой оболочки можно в первом приближении оценить по количеству встроенных в нее команд. То есть - команд, выполняемых внутри самого шелла, без порождения новых процессов, как это происходит при выполнении внешних команд. Очевидно, что такие встроенные команды будут выполняться быстрее и отъедать меньше ресурсов. Что, конечно, не скажется при интерактивной работе на современных машинах, но вот в сложных сценариях - вполне может.

В zsh поддерживается весь набор встроенных команд, стандартизированный для POSIX shell, большинство команд из развитых оболочек bash и tcsh, ну и, разумеется, специфичные для этого шелла команды. Общее число их превышает 80 - примерно столько же, сколько встроено в tcsh и bash, вместе взятые.

Очень интересна (и удобна) в zsh работа с командными конструкциями перенаправления. Здесь и множественное перенаправление вывода, когда результат выполнения команды направляется сразу в несколько файлов, и множественное перенаправление ввода - когда команда, напротив, получает аргументы последовательно из более чем одного файла, перенаправление без команды, когда конструкция типа

$ <> 

просто выведет на экран содержимое указанного файла - без привлечения команд типа cat или less.

При перенаправлении возможна группировка команд по шаблону. Так, файлы с именами вида file1 и file2 можно просмотреть одной командой

$ <>

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

$ sort <>

отсортирует содержимое обоих файлов точно так же, как это сделал бы конвейер команд

$ cat file1 file2 | sort

Наконец, еще одна специфическая особенность zsh - т.н. пред-исполнимая модификация команд (precommand modifier), осуществляющаяся перед их интерпретацией. Именно таким образом можно отменить чрезмерно навязчивую автокоррекцию аргументов для одной отдельно взятой команды, например, копирования:

$ nocorrect cp file1 file2

Легко видеть, что все изобилие возможностей zsh далеко выходит за рамки стандарта POSIX для командных оболочек. Однако, в подтверждение своего соответствия оному, zsh, наступая на горло собственной песне, способен к эмуляции POSIX Shell - для этого достаточно создать файл /bin/sh как символическую ссылку на исполнимый файл zsh, например:

$ ln -s /usr/bin/zsh /bin/sh

Впрочем, делать это следует только в случае полной уверенности, что все общесистемные скрипты полностью совместимы с zsh - а такая уверенность может быть только в том случае, если они написаны собственноручно:-). Я успешно применял zsh в качестве общесистемного шелла в самостройном Linux'е (по мотивам Linux from Scratch). Однако в других дистрибутивах (и тем более во FreeBSD) от этого лучше воздержаться.

Кроме того, имеется и некий режим совместимости с командными оболочками csh-клана (в нем я, впрочем, не разбирался).

Здесь перечислена лишь небольшая часть возможностей оболочки zsh, в частности, я не останавливался на его встроенных функциях, хотя именно они и есть та база, что обеспечивает все описанное (и не описанное) богачество возможностей. Не говорил я и о подгружаемых модулях (по типу plug-ins) - а ведь среди последних есть даже собственный ftp-клиент. Ибо для этого потребовалось бы пересказать всю экранную документацию к нему - более дюжины man-страниц общим объемом (в *.gz-виде) свыше 250 Кбайт, плюс официальное руководство с сайта проекта, включающее в pdf-формате 260 страниц (к слову - автором этой документации является тот же Петер Стефенсон).

Тем не менее надеюсь, что я убедил вас в том, что zsh - штука стоящая. Если так - то

Приступаем к установке

Оболочка zsh стандартно входит в большинство (я бы сказал - во все из мне известных) полнофункциональных дистрибутивов Linux, в качестве портов и пакетов доступна во Free- и OpenBSD (на счет NetBSD - просто не помню), портирована даже в AtheOS (вернее - ее отпрыска Syllable). Так что проблем с ее получением быть не должно. Устанавливаем zsh штатным для данного дистрибутива методом и переходим к следующему разделу.

Если же почему-либо zsh в составе дистрибутива не обнаружился - отправляемся на ftp://ftp.zsh.org/pub/ или какое-либо его зеркало (список - на hcodep://www.zsh.org), можно - в каталог distfiles ftp-серверов FreeBSD или Gentoo. И качаем в свое удовольствие исходники последней стабильной версии (на данный момент - 4.07), или, при желании, разрабатываемой (ныне - 4.2) - различий в стабильности между ними я не заметил.

Исходники zsh распаковываются и собираются обычным образом - ./configure, make, make install, никаких неожиданностей здесь не предвидится. Единственно, я предварительно, чисто для интереса, поинтересовался бы опциями конфигурирования -

$ ./configure --help

Из которых не побрезговал бы опцией --bindir=/bin - это будет полезно, если zsh будет использоваться как login shell.

В дистрибутивах Linux, предусматривающих автоматическое обновление общесистемных профильных файлов (например, в Gentoo), может возникнуть необходимость в том, чтобы zsh брал свои переменные окружения из какого-либо общего конфига, например, /etc/profile. Для этого при начальном конфигурировании исходников следует указать

$ ./configure --enable-zprofile=/etc/profile

И еще: ручная сборка zsh может потребоваться в некоторых пакетных дистрибутивах, в которых, будучи установленным из бинарников, он может работать неподобающим образом (о причинах я скажу позже).

Теперь же, установив zsh тем или иным образом, делаем его своей пользовательским шеллом по умолчанию - login shell (одной из команд типа usermod, pw, chsh) и -

Начинаем настройку

Без этого нам, скорее всего, не обойтись. Дело в том, что в свежеустановленном zsh, мы имеем шанс не увидеть почти ничего из описанных выше прелестей - ни развертывания сокращений путей, ни автодополнений опций и аргументов, ни выразительных приглашений командной строки. Перед нами будет безликая строка с именем машины (типа localhost%), которая едва-то будет справляться с обычным автодополнением команд и путей (и то - не обязательно). Могут возникнуть проблемы даже с вызовом собственной экранной документации man zsh. Почему?

Дело в том, что zsh имеет очень богатый набор собственных конфигурационных файлов, о которых я скажу чуть ниже. Но при установке его эти файлы не помещаются автоматически ни в каталог /etc, ни в домашний каталог пользователя - то есть ни в одно из тех мест, где их можно было бы ожидать. Конечно, совсем без первичных настроек zsh не останется: он прекрасно воспринимает их из таких общесистемных профильных файлов, как /etc/profile, /etc/login и т.д. Однако, во-первых, для этого он должен быть собран должным образом. А во-вторых, и вести себя при этом он будет почти точно также, как и соответствующие оболочки (bash или tcsh, а то и /bin/sh - почему, например, во FreeBSD zsh по первости не способен даже к автодополнению команд). От одного из этих профильных файлов zsh унаследует и переменные окружения, включая MANPATH - почему подчас не сможет найти и свою собственную документацию.

Так что для придания zsh полного блеска следует прибегнуть к его собственным конфигурационным файлам. Правда, сначала придется отыскать их примеры из штатной поставки - должен заметить, что в разных системах они могут обнаружиться в весьма неожиданных местах. Так, во FreeBSD их штатное место - каталог /usr/local/share/examples/zsh, в дистрибутиве Gentoo Linux примеры оказываются в /usr/share/doc/zsh-XXX-rX/StartupFiles, в иных - вполне могут оказаться где-нибудь в районе /usr/share/zsh, и т.д.

Для минимизации времени на поиски выдам секрет - примеры конфигурационных файлов zsh, входящие в штатный комплект, называются - zlogin, zshenv и zshrc (возможно, с расширением .gz), и благодаря моей доброте :-) их не трудно будет отыскать командой find - за пределы каталога /usr файлы эти попасть не должны (даже в том случае, если, как в Gentoo, сам исполняемый файл zsh оказывается в каталоге /bin).

После изыска конфигов для сердца вольного есть два пути. Первый - простой, копируем их в наш домашний каталог в качестве dot-файлов (~/.zlogin, ~/.zshenv и ~/.zshrc, соответственно), после чего наслаждаемся жизнью. В ряде случаев этого достаточно, чтобы получить доступ к базовым (но очень даже расширенным, сравнительно с собратьями) возможностям этого шелла.

Разумеется, суперпользователь может скопировать эти файлы и в каталог /etc (без точек в имени) - в этом случае они будут определять конфигурацию zsh для всех пользователей, его запускающих (любым образом, о чем - чуть ниже).

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

Первый шаг на этом пути - задаться вопросом, а зачем zsh'у так много конфигов, если другие шеллы спокойно обходятся двумя (а то и одним, как /bin/sh). На это я отвечу, что конфигов в zsh вовсе не много, а очень много: в дополнение к трем примерным в разделе FILES его man-страницы можно найти упоминание еще о zprofile и zlogout (и, соответственно, ~/.zprofile и ~/.zlogout). А в ходе пользования им вы, скорее всего, увидите в своем каталоге еще и такие файлы, как ~/.zcompdump и ~/.zhistory.

Так для чего нам такое богачество? Чтобы разобраться в этом, вспомним о трех видах функционирования любого шелла - неинтерактивном, интерактивном и подвиде последнего - главном пользовательском (login shell). Так вот, файл /etc/zshenv (или ~/.zshenv) считывается при каждом запуске любого экземпляра zsh, независимо от того, происходит он интерактивно или опосредованно. Настройки файла /etc/zshrc~/.zshrc) имеют силу для любого интерактивного запуска zsh. И, наконец, файлы /etc/zlogin и /etc/zprofile оба вместе (как и соответствующая им пара ~/.zlogin и ~/.zprofile) относятся только к тому экземпляру интерактивно запущенного zsh, который выступает в качестве login shell.

Зачем так сложно? А для того, чтобы можно было гибко (и индивидуально) настроить неинтерактивные, интерактивные и пользовательские экземпляры шеллов. Действительно, очевидно, что на настройку неинтерактивного шелла влияет только содержимое файла /etc/zshenv~/.zshenv), на настройку любого интерактивно запущенного экземпляра - уже он же вкупе с /etc/zshrc~/.zshrc), тогда как поведение login shell определяется кумулятивным эффектом всех трех (или даже четырех) их пар.

Хорошо, но зачем же нам два конфига для login shell? - спросите вы меня. Ответ прост - из соображений совместимости с bash и tcsh. Для пояснения чего вернемся к истории вопроса. В первозданном шелле Борна существовал только один конфиг - /etc/profile (~/.profile) для любых экземпляров шелла. В bash к нему прибавился еще и файл /etc/bashrc (~/.bashrc) для интерактивного использования (считываемые, естественно, после предыдущего - как более молодой по происхождению).

В csh же набор конфигов был совсем иной. Там изначально существовали два конфига - /etc/csh.env (~/.csh.env) на все случаи жизни и /etc/login (~/.login) - в качестве конфигуратора login shell, считываемые именно в таком порядке.

В zsh же, дабы удовлетворить привычки пользователей любых предшествовавших шеллов, были включены оба "логируемых" конфига, причем порядок их считывания был унаследован от каждого из родителей. В результате получилась довольно сложная последовательность при запуске login shell:

zshenv -> zprofile -> zshrc -> zlogin

Причем каждый конфиг сначала, естественно, считывается из каталога /etc, а затем из домашнего каталога пользователя берется его аналог. Разумеется, если все четыре файла присутствуют (и там, и там). Что, сразу скажем, отнюдь не обязательно. Очевидно, что совместное использование zprofile и zlogin ни малейшего смысла не имеет. Просто бывшим пользователям bash привычней первая схема запуска login shell, бывшим приверженцам tcsh - вторая. Забегая вперед, замечу, что вообще пользователь может обойтись только одним конфигом в своем домашнем каталоге (например, ~/.zshrc для любого интерактивного экземпляра шелла - ведь login shell также будет интерактивным), а все общие настройки получать из общесистемного конфига (например, /etc/profile). Более того, в дистрибутиве Gentoo Linux именно так поступить лучше всего - к причинам вернусь позднее.

Осталось объяснить смысл остальных dot-файлов из пользовательского каталога. Каковой, впрочем, ясен из названий: ~/.zlogout - это сценарий, отрабатываем при выходе из login shell, ~/.zhistory хранит историю команд (это и есть ее буфер), а ~/.zcompdump (насколько я понимаю) - делает то же самое, но для встроенных функций zsh. Два последних файла возникают (при выполнении некоторых условий) сами собой, и речи о них больше почти не будет.

Разобравшись с назначением dot-файлов, можно, наконец, выполнить

Собственно конфигурирование

Процесс этот начнем с того, что отделим зерна от плевел, то есть решим: а какие же именно файлы нужно настроить. Для начала я оставил бы в покое все файлы из каталога /etc - вернее, просто не стал бы копировать туда примеры. Почему? Да потому, что во многих дистрибутивах Linux имеются либо предварительно настроенные общесистемные конфиги, либо предусмотрены средства для их автоматического создания и обновления (ниже я продемонстрирую это на примере Gentoo). А во FreeBSD в каталоге /etc принято хранить только профильные файлы общесистемного шелла (сиречь /bin/sh, тогда как настройка пользовательского шелла - сугубо личное дело пользователя.

Так что ограничиваемся только нашим домашним каталогом. Однако и здесь многое зависит от ОС и дистрибутива. Так, в user-ориентированных дистрибутивах Linux первый кандидат на удаление (или не-копирование) - файл ~/.zshenv. Конечно, его можно создать в предельно облегченном виде (например, настройка приемов неинтерактивной работы здесь абсолютно лишняя). Характерно, что в штатном примере zshenv присутствует только переменная PATH. Но ведь ее можно получить из общесистемного /etc/profile, не так ли?

Особенно лишним выглядит ~/.zshenv для пользователей Gentoo. Как можно прочитать в соответствующей документации, здесь существует прекрасный механизм установки общесистемных переменных - env-update, автоматически обновляющий главный профильный конфиг /etc/profile. Так что при отсутствии ~/.zshenv все необходимое (в актуальном виде) будет браться оттуда. В присутствии же его - zsh откажется от считывания /etc/profile, в результате чего такие переменные, как локаль, EDITOR, PAGER и т.д., придется определять дополнительно.

А вот во FreeBSD и тех дистрибутивах Linux, которые (как, например, CRUX и Archlinux) не имеют средств автоматизации установки общесистемных переменных, файл ~/.zshenv оказывается практически необходимым. И ниже я попытаюсь обосновать это (а заодно и продемонстрировать, что в нем может содержаться).

Далее, решаем, а нужен ли нам отдельный конфиг для login shell, и если нужен - то какой из них - ~/.zlogin или ~/.zprofile. Теоретически, можно обойтись и без того, и без другого. Уж без последнего - так наверняка, не зря же его нет ни среди штатных примеров, ни среди многочисленных конфигов, которыми поделились с народом активные пользователи zsh (см. источники информации). А файл ~/.zlogin у меня, например, есть, хотя содержание его может варьировать от системы к системе.

Не помешает и файл ~/.zlogout - его возможное содержание также будет рассмотрено позднее.

Остается главный (по крайней мере, самый большой) конфиг - ~/.zshrc. Что с ним делать? Это легко уяснить, ознакомившись со штатным примером.

А в примере этом мы найдем и настройку приглашений командной строки, и определение псевдонимов - обычных и глобальных, для конвейеров команд, и определение переменной cdpath, и величину буфера истории команд, и многое, многое другое.

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

Начнем с последнего, то есть приглашения командной строки. В этом качестве могут использоваться:

  • полное или сокращенное имя хост-машины (последнее принято по умолчанию для первичного);
  • путь к текущему каталогу в различных формах;
  • номер текущей команды в буфере истории или строки в данном сеансе работы;
  • имя пользователя, или командной оболочки;
  • номер текущего терминала;
  • дата и время в разных форматах;
  • индикация работы от лица суперпользователя);
  • любые символы типа стрелок, крышечек, скобочек;
  • текстовые сообщения (например, поздравление с началом трудового процесса);
  • многое другое (подробности - в man zshhmisc).

Плюс к этому приглашения могут быть оформлены визуально различно: выделением жирным шрифтом (boldface mode) или повышенной яркостью (underline mode), инвертированием текста/фона (standout mode), а также цветами. Все это позволяет добиться максимальной информативности приглашения и его внешней выразительности.

Из опций настройки интерактивности - вспомним о том, что обеспечивает расширенные возможности автодополнения. А обеспечиваются они строками, следующими после комментария:

# Setup new style completion system. To see examples of the old style (compctl
# based) programmable completion, check Misc/compctl-examples in the zsh
# distribution.

и имеющими вид

autoload -U compinit
compinit

Вообще, штатный пример файла zshrc очень неплохо прокомментирован - по аглицки, но разобраться можно:-).

Далее - опции управления историей команд. Здесь для начала следует определить файл, в котором эта история будет храниться (в примере по умолчанию таковой отсутствует, и никакая история по умолчанию не сохраняется по выходе из сеанса zsh):

HISTFILE=~/.zhistory

Теперь - объем нашей исторической памяти, задаваемый двумя опциями -

HISTSIZE=1000

определяющей память текущего сеанса, и

SAVEHIST=1000

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

Для обеспечивая наращивания файла истории после каждого сеанса работы (в пределах установленной квоты) определяем:

setopt APPEND_HISTORY

Объем исторической памяти можно установить любым, однако не стоит расходовать его на повторение дублирующихся команд, ошибочными нажатиями Enter в пустой строке и т.д.:

setopt HIST_IGNORE_ALL_DUPS
setopt HIST_IGNORE_SPACE
setopt HIST_REDUCE_BLANKS

Можно настроить и еще много чего, но на этом пока закончим (некоторые опции будут подробнее рассмотрены в следующем разделе). Потому что наступило время подумать, а что представляют из себя установленные нами опции (типа compinit и т.д.)? А представляют они собой по преимуществу встроенные функции из комплекта zsh, которые в изобилии можно обнаружить в каталоге /usr/share/zsh/4.0.6/functions. И именно благодаря этим функциям и удается все настроить.

А еще - становится понятным, почему иногда zsh в пакетных дистрибутивах, устанавливаясь из бинарников, ведет себя не вполне подобающим образом. Да именно потому, что комплект функций в поставке и их использование в файлах примеров не вполне идентичны (по крайней мере, мне такие случаи встречались). При сборке же из исходников с сайта проекта, насколько мне довелось наблюдать, идентичность эта гарантирована. Тот же результат, естественно, достигается и в Source Based дистрибутивах (или, скажем, в системе портов FreeBSD).

Вообще говоря, я долго не мог понять, почему пользователи не любят zsh. И в конце концов пришел к выводу - именно потому, что сборщики дистрибутивов подчас не умеют его готовить. Это - не только мое мнение: многие пользователи пакетных дистрибутивов свидетельствуют, что установленный из "коробки" zsh в их системах работает весьма криво. Так что позволю себе привести

Личный рецепт приготовления

Как-то на одном форуме промелькнул вопрос - а как сделать, чтобы в командной строке zsh клавиши типа Delete, End, Home вели себя нормально (по умолчанию они этого делать не собираются). У меня до этого долго не доходили руки - я в этих целях привык к клавишным комбинациям (см. следующий раздел). Однако некоторое чувство дискомфорта преследовало: как же так, какой-то там bash умеет нормально обращаться с клавишами, а любимый zsh - не умеет. А тут и случай представился: во время затеянной в рамках мегатестирования тотальной пересборки Qt/KDE/иже_с_ними времени образовалось - вагон и маленькая тележка. И я наконец-то разобрался с клавишами в zsh. О чем с удовольствием рапортую в этом разделе.

Из многочисленных zsh-конфигов я (в настоящее время) использую: ~/.zshenv, считываемый при каждом запуске экземпляра оболочки (интерактивном и неинтерактивном), ~/.zshrc, считываемый при каждом интерактивном ее запуске, и ~/.zlogin, считываемый при каждом запуске zsh в качестве login shell. При этом установка переменных окружения происходит в стиле C-Shell: сначала из ~/.zshenv, затем из ~/.zshrc и, наконец, из ~/.zlogin.

Так что первым в моей схеме идёт ~/.zshenv. Он оказывает воздействие только при shell-скриптинге. Поэтому у меня он очень мал. В Linux-варианте (для Archlinux) он выглядит так:


#
# My ~/.zshenv for Linux
#export PATH="/bin:/usr/bin:\
/usr/local/bin:/usr/X11R6/bin:\
/opt/bin:/opt/kde/bin"

Во FreeBSD все примерно то же, но, естественно, /opt в PATH включать не требуется:

#
# My ~/. zshenv for FreeBSD
#
PATH="/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin"
QTDIR=/usr/local/qt
PATH="$PATH:$QTDIR"
MANPATH="$QTDIR/doc/man:$MANPATH"
LD_LIBRARY_PATH="$QTDIR/lib:$LD_LIBRARY_PATH"
export QTDIR PATH MANPATH LD_LIBRARY_PATH

Понятно, что оба файла можно записать поизящней (с точки зрения формы), но - так уж они исторически склались.

Основное внимание я уделяю файлу ~/.zshrc, так как им определяется поведение zsh не только при авторизации в консоли, но и при запуске терминальных окон в Иксах.


#
# My ~/.zshrc
#
# Path для поиска командой cd: то есть вместо cd $HOME/docs/editors/
# можно набирать просто cd editors
cdpath=(~/media ~/docs)

## Установка нормального поведения клавиш Delete, Home, End и т.д.:
case $TERM in
linux)
bindkey "^[[2~" yank
bindkey "^[[3~" delete-char
bindkey "^[[5~" up-line-or-history
bindkey "^[[6~" down-line-or-history
bindkey "^[[1~" beginning-of-line
bindkey "^[[4~" end-of-line
bindkey "^[e" expand-cmd-path ## C-e for expanding path of typed command
bindkey "^[[A" up-line-or-search ## up arrow for back-history-search
bindkey "^[[B" down-line-or-search ## down arrow for fwd-history-search
bindkey " " magic-space ## do history expansion on space
;;
*xterm*|rxvt|(dt|k|E)term)
bindkey "^[[2~" yank
bindkey "^[[3~" delete-char
bindkey "^[[5~" up-line-or-history
bindkey "^[[6~" down-line-or-history
bindkey "^[[7~" beginning-of-line
bindkey "^[[8~" end-of-line
bindkey "^[e" expand-cmd-path ## C-e for expanding path of typed command
bindkey "^[[A" up-line-or-search ## up arrow for back-history-search
bindkey "^[[B" down-line-or-search ## down arrow for fwd-history-search
bindkey " " magic-space ## do history expansion on space
;;
esac
# Примечание: если, скажем, в KDE для konsole
# выбрать тип Linux console, необходимости
# во второй секции нет. Консоль Linux в KDE можно

спокойно установить и во FreeBSD.
# К слову сказать, совсем уж нормального
# поведения клавиш в syscons я до сих пор
# не добился:-(

# Use hard limits, except for a smaller stack and no core dumps
unlimit
limit stack 8192
limit core 0
limit -s

# Установка атрибутов доступа для
# вновь создаваемых файлов
umask 022

# Исправление поведения less - для ликвидации
# лишних Esc и прочего безобразия при
# выводе man-страниц.
# Насколько мне известно, нужно только в некоторых
# дистрибутивах Linux
export LESS="-R"

# Установка alias'ов

## alias'ы для команд, не требующих коррекции, но требующих подтверждения

alias mv='nocorrect mv -i'
# переименование-перемещение c пogтвepжgeнueм

alias cp='nocorrect cp -iR'
# рекурсивное копирование с подтверждением

alias rm='nocorrect rm -i'
# удаление с подтверждением

alias rmf='nocorrect rm -f'
# принудительное удаление

alias rmrf='nocorrect rm -fR'
# принудительное рекурсивное удаление

alias mkdir='nocorrect mkdir'
# создание каталогов без коррекции
## Примечание: если не определить здесь nocorrect,
## zsh будет настойчиво предлагать подстановку
## существующих имен при создании каталогов,
## копировании и т.д.

## Разные полезные (ИМХО) alias'ы
alias h=history
alias grep=egrep

### вывод свободного дискового пространства
### в мегабайтах
alias df='df -m'

### Представление вывода less в more-подобном виде
### (с именем файла и процентом вывода)
alias less='less -M'

### Русский словарь для ispell по умолчанию
alias ispell='ispell -d russian'

## aliases для команды ls< ### показ классификации файлов в цвете и ### символически (Linux) alias ls='ls -F --color=auto' ### Во FreeBSD достаточно alias ls='ls -F' ### вывog в gлuннoм фopмaтe alias ll='ls -l' ### вывog всех файлов, включая dot-фaйлы, кромe . u .. alias la='ls -A' ### вывog вcex фaйлoв в gлuннoм фopмaтe, вkлючaя inodes alias li='ls -ial' ### вывод только каталогов alias lsd='ls -ld *(-/DN)' ### вывog тoльko dot-фaйлoв alias lsa='ls -ld .*' # Установка глобальных псевдонимов # для командных конвейеров alias -g M='|more' alias -g L='|less' alias -g H='|head' alias -g T='|tail' alias -g N='2>/dev/null'

# Ниже даны опции, относящиеся к функциям zsh,
# которыми собственно и определяется мощь этой оболочки

# Shell functions
setenv() { typeset -x "${1}${1:+=}${(@)argv[2,$#]}" }

# csh compatibility
freload() { while (( $# )); do; unfunction $1; autoload -U $1; shift; done }

# Where to look for autoloaded function definitions

fpath=($fpath ~/.zfunc)

# Autoload all shell functions from all directories in $fpath (following
# symlinks) that have the executable bit on (the executable bit is not
# necessary, but gives you an easy way to stop the autoloading of a
# particular shell function). $fpath should not be empty for this to work.
for func in $^fpath/*(N-.x:t); autoload $func

# automatically remove duplicates from these arrays
typeset -U path cdpath fpath manpath

# Указание путей к man-страницам.
## Linux:
manpath="/usr/man:/usr/share/man:\
/usr/local/man:/usr/X11R6/man:/opt/qt/doc"

## FreeBSD:
manpath="/usr/share/man:/usr/local/man:/usr/X11R6/man"

export MANPATH

# Список хостов, к которым будет применяться
# автодополнение при наборе в командной строке
# например, как аргументов браузера или
# ftp-клиента (see later zstyle)
hosts=('hostname' ftp.freebsd.org ftp.archlinux.org)

# Установка вида приглашения

## Обычное приглашение вида ~%=>
## (каталог от домашнего - пользователь/root - стрелка)
PROMPT='%~%#=> '

## Приглашения для второй линии многострочных команд
## вида #_строки>
PROMPT2='%i%U> '

## Приглашение с правой стороны экрана вида
## 19:15 vc/5 (время - номер консоли)
RPROMPT=' %T %y%b'

# Всякие переменные

## файл истории команд
## если не указан, история не будет сохраняться
## при выходе из сеанса
HISTFILE=~/.zhistory

## Число команд, сохраняемых в HISTFILE
SAVEHIST=5000

## Чucлo кoмaнд, coxpaняeмыx в сеансе
HISTSIZE=5000
## Примечание:
## рекомендуются равные значения для
## SAVEHIST и HISTSIZE

DIRSTACKSIZE=20

# Опции истории команд

## Дополнение файла истрии
setopt APPEND_HISTORY

## Игнopupoвaть вce пoвтopeнuя команд
setopt HIST_IGNORE_ALL_DUPS

## Игнopupoвaть лишние пpoбeлы
setopt HIST_IGNORE_SPACE

## Удалять из файл истории пустые строки
setopt HIST_REDUCE_BLANKS

# Установка-снятие опций шелла
setopt notify globdots correct pushdtohome cdablevars autolist
setopt correctall autocd recexact longlistjobs
setopt autoresume histignoredups pushdsilent noclobber
setopt autopushd pushdminus extendedglob rcquotes mailwarning
unsetopt bgnice autoparamslash

## Отключение звукового сигнала
## при ошибках
setopt No_Beep

## Нe cчuтaть Control+C зa выxog uз oбoлoчku
setopt IGNORE_EOF

# Autoload zsh modules when they are referenced
zmodload -a zsh/stat stat
zmodload -a zsh/zpty zpty
zmodload -a zsh/zprof zprof
zmodload -ap zsh/mapfile mapfile

# Опции общего поведения
# bindkey -v # режим навигации в стиле vi
bindkey -e # peжuм нaвuгaцuu в cтuлe emacs

bindkey ' ' magic-space # also do history expansion on space
bindkey '^I' complete-word # complete on tab, leave expansion to _expand

# Для разворота сокращенного ввода типа cd d/e в docs/editors
autoload -U compinit
compinit

# Completion Styles

# list of completers to use
zstyle ':completion:*::::' completer _expand _complete _ignored _approximate

# allow one error for every three characters typed in approximate completer
zstyle -e ':completion:*:approximate:*' max-errors \
'reply=( $(( ($#PREFIX+$#SUFFIX)/3 )) numeric )'

# insert all expansions for expand completer
zstyle ':completion:*:expand:*' tag-order all-expansions
# formacodeing and messages
zstyle ':completion:*' verbose yes
zstyle ':completion:*:descriptions' format '%B%d%b'
zstyle ':completion:*:messages' format '%d'
zstyle ':completion:*:warnings' format 'No matches for: %d'
zstyle ':completion:*:corrections' format '%B%d (errors: %e)%b'
zstyle ':completion:*' group-name ''
# match uppercase from lowercase
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
# offer indexes before parameters in subscripts
zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters

# command for process lists, the local web server details and host completion
#zstyle ':completion:*:processes' command 'ps -o pid,s,nice,stime,args'
#zstyle ':completion:*:urls' local 'www' '/var/www/htdocs' 'public_html'
zstyle '*' hosts $hosts

# Filename suffixes to ignore during completion (except after rm command)
zstyle ':completion:*:*:(^rm):*:*files' ignored-pacodeerns '*?.o' '*?.c~' \
'*?.old' '*?.pro'
# the same for old style completion
#fignore=(.o .c~ .old .pro)

# ignore completion functions (until the _ignored completer)
zstyle ':completion:*:functions' ignored-pacodeerns '_*'

# Флаги оптимизации для gcc
CFLAGS="-O3 -march=pentium4 -fomit-frame-pointer \
-funroll-loops -pipe -mfpmath=sse -mmmx -msse2 -fPIC"
CXXFLAGS="$CFLAGS"
BOOTSTRAPCFLAGS="$CFLAGS"
export CFLAGS CXXFLAGS BOOTSTRAPCFLAGS

И, наконец, файл ~/.zlogin. Что осталось неохваченным в ~/.zshrc и требуется только при авторизации в системе? Правильно, пользовательские переменные для определения терминала, редактора, пейджера и т.д.

#
# My ~/.zlogin for FreeBSD
#
from 2>/dev/null
EDITOR=joe
PAGER=less
TERM=${TERM:-cons25r}
export EDITOR PAGER TERM

Кроме того, в Linux'е здесь же резонно установить locale (во FreeBSD локаль лучше определять через класс пользователя). И потому для Linux - дополнение:


#
# My ~/.zlogin appendix for Linux
#
# Установка всех локально-зависимых переменных,
# кроме LC_ALL
export LANG="ru_RU.koi8r"

# Установка десятичной точки
# вместо запятой
# (требуется для некоторых счетных программ)
export LC_NUMERIC="POSIX"

Да, самое последнее - файл ~/.zlogout, отрабатываемый по завершении сеанса пользовательского шелла. У меня от включает две строчки

sync
clear

назначение которых более чем понятно (синхронизация дисковых кэшей и очистка экрана).

Это мои пользовательские конфиги. Почти те же самые я использую и для root'а, с минимальными коррективами. Так, переменная path в /root/.zshenv дополняется значениями

/sbin:/usr/sbin:/usr/local/sbin

В /root/.zshrc опция cdpath имеет вид

cdpath=(/etc /usr)

Кроме того, во FreeBSD здесь я исключаю переменные CFLAGS, CXXFLAGS и BOOTSTRAPCFLAGS, так как от лица root'а все собирается через систему портов, где их аналоги определяются в файле /etc/make.conf.

А в /root/.zlogin локаль (в Linux) установлена как

export LANG="POSIX"

так как некоторые программы упорно не желают собираться при какой-либо иной.

Заключительные замечания

Я уже упоминал, что проект zsh прекрасно документирован, один User's Guide чего стоит. Однако внимание прессы, как онлайновой, так и бумажной, к нему явно недостаточно. И потому из дополнительных источников информации на ум приходит только (если не считать отрывочных упоминаний в паре книжек по Linux) статья Мэтта Чапмена (Macode Chapman) - Curtains up: introducing the Z shell. Да еще недавно появился на русском языке материал Алексея Отта Командный процессор Zsh

Много полезного можно узнать из анализа dot-файлов различных пользователей, ссылки на которые отыскиваются как на www.zsh.org, так и в иных местах. Собирать все эти ссылки мне было лениво (да подчас я и не помню, где их отыскал), так что все свои находки я собрал воедино в виде отдельного тарбалла.