- PVSM.RU - https://www.pvsm.ru -
Чтобы перейти с bash на zsh необходимо знать базовые отличия между ними — без этого будет сложно провести первоначальную настройку zsh в ~/.zshrc
.
Я не нашёл краткого описания этих отличий когда переходил сам, и мне пришлось потратить немало времени на вычитывание документации zsh. Надеюсь, эта статья упростит вам переход на zsh.
Для начала — а стоит ли вообще тратить своё время и внимание на переход? Учить ещё один диалект sh, менее распространённый чем POSIX sh или bash, заново заниматься настройкой рабочего окружения…
На мой взгляд, если вы проводите много времени в консоли, вам нравятся Vim или Emacs и вы уже потратили немало времени на их настройку "под себя" — однозначно стоит! Zsh по духу очень на них похожа: это очень сложная и гибкая программа, чьи возможности полностью мало кто знает, но потратив некоторое время на настройку можно получить очень удобную лично вам рабочую среду.
Что касается изучения нового диалекта sh… пользы от этого, скорее всего, действительно мало, но описанного в этой статье минимума должно быть достаточно чтобы настраивать zsh, а писать новые скрипты на диалекте zsh вам никто и не предлагает. В общем и целом это ничем не отличается от необходимости минимально знать VimL или Emacs Lisp исключительно для настройки Vim/Emacs.
В инете куча статей и презентаций описывающих конкретные фичи zsh, значительно упростившие чью-то жизнь. Я не буду их перечислять, потому что фичи всем нужны разные, и в zsh найдутся варианты на любой вкус. Вместо этого я опишу ключевые особенности zsh, которые позволили реализовать эти фичи:
<Tab>
в разных местах командной строки будут дополняться разные вещи: имена команд, их параметры, файлы, имена пользователей и серверов, номера процессов, названия переменных, индексы массивов и ключи хешей, элементы синтаксиса zsh, названия цветов и шрифтов, сетевых интерфейсов, системных пакетов… короче, вообще всего что можно автодополнять. И его можно детально контролировать, вплоть до изменения логики автодополнения для конкретного контекста у конкретной команды.Ещё раз уточню, что я буду описывать именно отличия от bash, а не полный набор возможностей zsh. Большая часть привычного вам функционала работает в zsh точно так же, как и в bash. Но при этом часто есть специфичные для zsh способы делать примерно то же самое. Это связано с тем, что в zsh уделяется очень много внимания совместимости с другими шеллами, поэтому в zsh плюс к своим фичам перетащили очень многое из других шеллов — и в результате получили несколько альтернативных способов делать одно и то же.
$@
, $1
, …).${(kv@)some_hash}
. Для шаблонов могут быть в начале или середине: *CaseImportant(#i)CaseIgnored*.txt
.*(/^F)
.$PWD:h:t
, ${some_param:h:t}
. Для шаблонов задаются перед закрывающей круглой скобкой квалификаторов: *(:e)
.Многие встроенные команды выводят текущее состояние при запуске без аргументов (плюс нередко у них есть аргумент, который оформляет вывод в стиле команд zsh, что довольно удобно).
# текущие опции
setopt
# полный список всех опций
setopt KSH_OPTION_PRINT; setopt
# список обрабатываемых кнопок в текущем режиме
bindkey
# список обрабатываемых кнопок во всех режимах, в формате команд zsh
for m in $(bindkey -l); bindkey -M $m -L
# текущие стили (контекстно-зависимые настройки)
zstyle
zstyle -L
# текущие алиасы (обычные плюс глобальные), в формате команд zsh
alias -L
# текущие алиасы для суффиксов, в формате команд zsh
alias -s -L
# текущие параметры (переменные)
typeset
# текущие параметры (переменные), в формате команд zsh
typeset -p
Это далеко не полный список, но для большинства задач в процессе (анализа) настройки zsh его должно хватить.
Ещё может быть полезным запуск zsh -f
— это запускает zsh в состоянии по умолчанию (без выполнения любых стартовых скриптов кроме /etc/zshenv
, которого в большинстве систем и так нет).
setopt
с префиксом "no" и unsetopt
без "no" (равно как и наоборот!) делают одно и то же.setopt
используются маленькие буквы без подчёркиваний, в документации используются большие буквы с подчёркиваниями. Это создаёт некоторое неудобство — при поиске в документации нужно догадаться, где вставлять подчёркивания чтобы найти нужную опцию.emulate
позволяет массово установить группу опций в состояние совместимости с sh, ksh, csh или в состояние по умолчанию для zsh. Многие функции в zsh начинаются командой emulate -L zsh
, что позволяет на время выполнения функции привести ключевые опции в состояние по умолчанию для zsh — без этого большинство нетривиальных функций может ломаться из-за выставленных пользователем опций (например, есть опция которая управляет тем, как индексируются массивы — от 1 или от 0).# все эти команды делают одно и то же
setopt nonumericglobsort
setopt NO_numericglobsort
setopt NO_NUMERIC_GLOB_SORT
setopt _N_O_numERICglob_SORT_
unsetopt NUMERIC_GLOB_SORT
unsetopt numericglobsort
В начале использования zsh, для более привычной работы после bash, я бы рекомендовал следующие опции:
# традиционный стиль перенаправлений fd
unsetopt MULTIOS
# поддержка ~… и file completion после = в аргументах
setopt MAGIC_EQUAL_SUBST
# не обрабатывать escape sequence в echo без -e
setopt BSD_ECHO
# поддержка комментариев в командной строке
setopt INTERACTIVE_COMMENTS
# поддержка $(cmd) в $PS1 etc.
setopt PROMPT_SUBST
Ещё есть опция SH_WORD_SPLIT
, и формально для привычной работы после bash её тоже надо включить, но я бы этого не рекомендовал: поведение zsh без этой опции более удобное и логичное, лучше сразу к нему привыкать. Она отвечает за то, как сработает cmd $PARAM
если значение $PARAM
это строка содержащая пробелы: в bash cmd
получит несколько аргументов, а в zsh — один (как если бы вызвали cmd "$PARAM"
). А если $PARAM
это массив, то zsh передаст cmd
по одному аргументу на каждый не пустой элемент массива (даже если эти элементы содержат пробелы).
(В основном, эта статья описывает поведение zsh с опциями по умолчанию, иначе каждое второе предложение пришлось бы уточнять в стиле "но вот при таких-то опциях всё это работает иначе".)
$БОЛЬШИЕ
буквы, а для массивов (обычных и ассоциативных) — $маленькие
.typeset -U
можно объявить массив с уникальными элементами (попытки добавления уже существующих элементов будут игнорироваться).typeset -T
можно связать массив со скаляром в формате $PATH
. Несколько таких связанных параметров уже созданы: $PATH
и $path
, $FPATH
и $fpath
, $MANPATH
и $manpath
, $CDPATH
и $cdpath
. Для связанных параметров не имеет значения какой из них мы изменяем — изменяются сразу оба. Поэтому в zsh с такими параметрами практически всегда работают через массивы ($path
, $fpath
, …) — это значительно удобнее.$PS1
, $PROMPT
и $prompt
(хотя, это скорее просто синонимы для одного параметра).*([2,-2])
.**/
— совпадает с подкаталогом любого уровня вложенности, включая отсутствие подкаталога<число1-число2>
— совпадает с числом в заданном диапазоне в имени файла, и начало и конец диапазона можно не указывать(шаблон1|шаблон2)
— альтернатива (так же — группирующие скобки при использовании опции EXTENDED_GLOB
)EXTENDED_GLOB
, то в шаблонах можно будет дополнительно использовать #
(повтор предыдущего элемента), ~
и ^
(исключение из совпадения)# показать файлы в текущем каталоге или его подкаталогах,
# которые содержат в имени число большее или равное 5 либо строку example,
# и у которых расширение .txt
ls -l **/*(<5->|example)*.txt
Квалификаторы есть только у шаблонов, они позволяют задать дополнительные условия отбора файла: по типу (файл/каталог/симлинк/etc.), правам, времени (изменения/etc.), размеру… Можно сортировать и индексировать отобранные файлы. Можно включить для конкретно этого шаблона совпадение начальной *
с именами начинающимися на точку. Можно включить удаление этого шаблона из аргументов командной строки если он не совпал ни с одним файлом.
# до 5-ти подкаталогов текущего каталога,
# имена которых могут начинаться на точку и содержат "a",
# которые изменялись последними
ls -ld *a*(D/om[1,5])
Если включить опцию EXTENDED_GLOB
, то в шаблонах можно будет использовать флаги: для файлов интерес представляет управление чувствительностью к регистру, а при совпадении с параметром/строкой есть и другие полезные флаги.
# эти команды идентичны
ls -ld .[cC][oO][nN][fF][iI][gG]*
setopt extendedglob; ls -ld .(#i)Config*
Для параметров доступно намного больше флагов: вывод всех (включая пустые) элементов массива даже в кавычках, выполнение join или split по заданной подстроке, вывод только ключей и/или значений ассоциативного массива, экранирование разными видами кавычек и обратная операция, etc.
# вывод ключей ассоциативного массива вместо значений
echo ${(k)some_hash}
# преобразовать $PATH в массив разделив на элементы по ":",
# после чего корректно взять каждый элемент в одинарные кавычки
echo ${(s<:>qq)PATH}
И для шаблонов и для параметров можно использовать модификаторы: удаление последнего элемента пути, удаление всех элементов пути кроме последнего, удалить/оставить расширение, экранирование и обратная операция, поиск и замена подстроки, etc.
# вывод имени родительского каталога (сначала отбрасываем последний
# элемент пути, потом отбрасываем все элементы пути кроме последнего)
echo $PWD:h:t
# вывести имена (без каталога) всех симлинков в любом подкаталоге,
# заменив в них подстроку "fil" на "FIL" (если такая подстрока есть)
echo **/*(@:t:s/fil/FIL/)
Помимо традиционного способа подгружать код через source /path/to/file.sh
или . /path/to/file.sh
в zsh активно используется автозагрузка кода в момент первого вызова функции.
Для поиска файла с нужной функцией используется $FPATH
— переменная аналогичная по формату $PATH
, содержащая список каталогов в которых выполняется поиск файла с именем, идентичным имени загружаемой функции.
При вызове autoload
никаких файлов с диска не считывается, и даже не проверяется их наличие — всё это произойдёт при первом вызове функции. Практически всегда необходимо передавать autoload
аргументы -U
(отменяет эффект текущих alias-ов для загружаемого файла, потому что нередко alias-ы настроенные пользователем могут нарушать работу сторонних функций) и -z
(необязательное уточнение что загружаемый файл — в формате zsh, но безопаснее его всегда задавать).
fpath=(~/my-zsh-functions $fpath)
autoload -Uz fn
fn
При этом содержимое файла ~/my-zsh-functions/fn
может быть в одном из этих трёх форматов:
# Просто набор команд, без каких-либо функций:
echo "Я функция fn"
# Одна функция с именем совпадающим с именем файла:
fn() {
echo "Я функция fn"
}
# Набор из любых команд и функций, включая fn:
fn() {
fn2
}
fn2() {
echo "Я хелпер функции fn"
}
echo "Выполнится перед первым запуском fn"
# Но файл должен содержать явный вызов fn:
fn "$@"
echo "Выполнится после первого запуска fn"
При первом запуске zsh нередко оказывается, что часть кнопок вроде F1/Backspace/Delete/курсора работает некорректно. Это связано с тем, что абсолютное большинство консольных приложений использует readline и корректная настройка этих кнопок считывается из /etc/inputrc
и ~/.inputrc
, а zsh этого не делает.
Проблема решается в лоб — нужно посмотреть, какие escape-последовательности выдают нужные кнопки в вашем терминале и задать в ~/.zshrc
нужные обработчики для этих escape-последовательностей. Примерно так:
bindkey '^[[A' up-line-or-history # Up
bindkey '^[[B' down-line-or-history # Down
# и т.д.
Смотреть выдаваемые кнопками последовательности можно запустив cat >/dev/null
и нажимая Ctrl-V
перед нужной кнопкой. (И таки да, занимаясь этим в 2017 я чувствовал себя немного странно…) Но в комплекте с zsh идёт вспомогательная утилита zkbd
, которая автоматизирует этот процесс. Для этого необходимо подключить её в ~/.zshrc
, после чего у вас появится ассоциативный массив $key
содержащий нужные escape-последовательности:
autoload -Uz zkbd
[[ ! -f ~/.zkbd/$TERM-${${DISPLAY:t}:-$VENDOR-$OSTYPE} ]] && zkbd
source ~/.zkbd/$TERM-${${DISPLAY:t}:-$VENDOR-$OSTYPE}
[[ -n $key[Up] ]] && bindkey -- $key[Up] up-line-or-history
[[ -n $key[Down] ]] && bindkey -- $key[Down] down-line-or-history
# и т.д.
Я не уточняю детально какие команды (вроде up-line-or-history
) на какие кнопки назначать потому, что во-первых назначать надо не все подряд, а только те, которые у вас из коробки не заработают, и во-вторых если мнения насчёт того, что должны делать Home или Backspace у всех сходятся, то вот поиск в истории по Up и Down может выполняться довольно разными способами, и функции в этих случаях на эти кнопки надо назначать тоже разные.
(Кстати, задавать символ Escape (^[
) в параметре bindkey можно и настоящим символом, вводя его через Ctrl-V
, и двумя обычными символами ^[
, и двумя символами e
.)
Это встроенный способ использовать контекстно-зависимые настройки. Он во многом похож на обычные параметры, только помимо имени и значения параметра zstyle
позволяет задать шаблон "контекста". А потом получать значения относящиеся к текущему контексту. Этот подход активно используется для настройки работы автодополнений, но им можно пользоваться и для своих скриптов.
# установим значение my-param=default для 3-х уровневого контекста,
# где на первом уровне идентификатор нашего приложения (у всех
# приложений общая база zstyle, так что свои настройки надо изолировать)
# а на следующих двух уровнях любые значения
% zstyle ':my-app:*:*' my-param default
# установим значение my-param=val-one для контекста, у которого на
# втором (более приоритетном) уровне будет значение "one"
% zstyle ':my-app:one:*' my-param val-one
# установим значение my-param=val-two для контекста, у которого на
# третьем (менее приоритетном) уровне будет значение "two"
% zstyle ':my-app:*:two' my-param val-two
# получаем значение my-param в переменную result для заданного контекста
% zstyle -s ':my-app:a:b' my-param result
% echo $result
default
% zstyle -s ':my-app:one:b' my-param result
% echo $result
val-one
% zstyle -s ':my-app:a:two' my-param result
% echo $result
val-two
% zstyle -s ':my-app:one:two' my-param result
% echo $result
val-one
Часть дополнительного функционала zsh реализована не в обычных скриптах подгружаемых через autoload -Uz
, а как системные библиотеки *.so
. Они используются, например, для предоставления доступа к регулярным выражениям PCRE, математическим функциям, сокетам, etc. Такие библиотеки подгружаются через zmodload
.
Для перехвата сигналов помимо стандартного trap '…;code;…' INT
можно использовать функции с особыми именами: TRAPINT() { …;code;… }
.
У многих конструкций вроде if
, while
, etc. есть сокращённая форма (пример есть выше, где выводилось значение всех режимов bindkey).
Внезапно, zsh-специфичный аналог echo
— команда print
— оказалась весьма удобной при изучении zsh. Она много чего умеет, но из самого полезного:
# вывод по одному аргументу на строку, удобно для массивов
print -l $path
# вывод по два аргумента на строку в столбцах,
# удобно для ключей и значений ассоциативных массивов
print -a -C 2 "${(kv@)ZSH_HIGHLIGHT_STYLES}" | sort
# вывод используя %-последовательности используемые в $PS1
print -P '%Bbold%b %F{red}current%f dir is: %~'
Если Когда решитесь переходить на zsh, то для принятия конкретных решений про фреймворки/модули/темы вам пригодится Awesome-коллекция всего для zsh [2].
Автор: Alex Efros
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/bash/252820
Ссылки в тексте:
[1] менеджера плагинов zsh Аntigen: https://github.com/zsh-users/antigen
[2] Awesome-коллекция всего для zsh: https://github.com/unixorn/awesome-zsh-plugins
[3] Источник: https://habrahabr.ru/post/326580/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.