Не понимаю, и как я раньше жил без Zsh?

21 января 2013

Впервые я прочитал о Z-Shell несколько лет назад, но тогда эта оболочка почему-то не произвела на меня большого впечатления. А недавно коллега сообщил мне, что уже давно ею пользуется (оказывается, zsh появился аж в 1990-м году) и категорически доволен. Вот я и решил попробовать. Оказалось, что zsh — это офигенная штука. В чем я сейчас и буду пытаться вас убедить.

Установка

В Debian/Ubuntu установка zsh и прописывание его пользователю производится следующим образом:

sudo apt-get install zsh
sudo usermod eax -s /usr/bin/zsh

А в случае FreeBSD:

pkg_add -r zsh
pw usermod eax -s /usr/local/bin/zsh

Если оболочка уже установлена, а у вас нет прав рута, то можете прописать exec zsh в файл ~/.bashrc. Этот же прием можно использовать, если ваш оконный менеджер не видит новые настройки без перезапуска.

При первом запуске zsh просит произвести его настройку. Там нет ничего сложного, вы сами легко во всем разберетесь. Главное — ставьте автокомплит на максимум.

Автокомплит

Крутой автокомплит, пожалуй, является главной фишкой zsh. Даже если вы не будите пользоваться никакими другими возможностями zsh, на него стоит перейти только ради автокомплита. Хотя, если подумать, практически все удобство zsh заключается в возможности сделать больше, вводя меньше символов, что можно отнести к автокомплиту…

Помимо обычного дополнения имен программ и путей к файлам, что есть и в bash, zsh умеет дополнять аргументы команд. Например, git br[TAB] будет дополнено до git branch. Если вы скажете git checkout [TAB], то увидите список веток. С помощью повторного нажатия Tab вы можете подставить имена веток в команду. Или вы можете ввести часть имени ветки, а остальное за вас введет zsh после нажатия Tab.

Это работает не только с Git. Например, для команды kill вам будут предложены ID процессов, для killall — имена процессов, для su — имена пользователей, для pkg_add — пути к пакеджам в /usr/ports/packages/All/ и тд. Также zsh умеет выводить подсказки по ключам программ, например, в ответ на sudo -[TAB].

Если вы наберете scp user@example.ru:[Tab], zsh даже сходит за вас по SSH на удаленный сервер, получит список директорий и предложит их для автокомплита. Разумеется, это возможно, только если вы предварительно настроите к example.ru SSH-доступ по identity file.

Дополнение: Я тут с удивлением понял, что все описанное выше есть и в bash, но не во всех системах/дистрибутивах присутствует «из коробки». Следует также отметить, что автокомплит в zsh продуман лучше. Например, команде kill предлагаются только ID процессов текущего пользователя. Притом помимо самих циферок также выводятся имена процессов и другая информация.

В zsh имеется встроенная поддержка автокомплита для сотен программ. Также вы можете настроить автодополнение для программ, неизвестных zsh. Например, чтобы добавить актокомплит для rebar, нужно добавить в ~/.zshrc следующее:

rebar_args=(clean compile escriptize create create-app \
            create-node list-templates check-deps get-deps \
            update-deps delete-deps list-deps generate \
            overlay generate-upgrade generate-appups eunit \
            ct qc xref help version)
compctl -k rebar_args rebar

Zsh позволяет очень быстро вводить длинные пути. Например, команда:

cd p/s/d/p

… после нажатия Tab превращается в:

cd projects/sandbox/dotfiles/portege

Это реальный путь, который мне приходится довольно часто вводить. В директории dotfiles/portege закрытого репозитория sandbox у меня хранятся конфигурационные файлы, которые я использую на своем Toshiba Portege Z930. На первый взгляд такое автодополнение кажется не слишком полезным (ведь с тем же успехом можно несколько раз нажать Tab), но на практике оно очень удобно.

Zsh поддерживает «умный cd» c двумя аргументами. Вот как это работает:

$ pwd
~/projects/sandbox/erlang/key_value/src
$ cd key_value mnesia
~/projects/sandbox/erlang/mnesia/src

Если в ~/.zshrc прописано setopt autocd, то можно вводить пути к каталогам вообще без команды cd. Оболочка сама догадается, что вы хотите перейти в директорию, а не запустить ее, как программу. Аналогично можно ассоциировать расширения файлов с программой для их открытия:

alias -s {erl,ml,hs}=vim
alias -s avi=mplayer

Если теперь ввести имя erl-файла, он автоматически будет открыт в Vim.

Промты

В отличие от bash zsh имеет два промта — левый и правый. Правый промт исчезает при вводе длинных команд, что делает его очень удобным для отображения не самой полезной информации, типа времени или текущего каталога. Промты настраиваются с помощью переменных PROMPT и RPROMPT:

export PROMPT='%n@%m> '
export RPROMPT='[%~]'

Некоторые из специальных последовательностей, которые можно в них использовать:

Последовательность Описание
%n Имя пользователя
%m Имя компьютера (до первой точки)
%M Полное имя компьютера
%~ Путь к текущему каталогу относительно домашнего
%d Полный путь к текущей директории
%T Время в формате HH:MM
%* Время в формате HH:MM:SS
%D Дата в формате YY-MM-DD
%B, %b Начало и конец выделения жирным
%U, %u Начало и конец подчеркивания

С помощью опции prompt_subst можно делать так:

git_prompt() {
  temp=`git symbolic-ref HEAD 2>/dev/null | cut -d / -f 3`
  if [ "$temp" != "" ]; then echo "$temp:"; fi
}
setopt prompt_subst
export RPROMPT='[$(git_prompt)%~]'

Если мы находимся в git-репозитории, в правом промте будет отображено имя бранча и путь к директории, разделенные двоеточием. Иначе будет отображен только путь к текущему каталогу.

Глобинг

Опция extendedglob включает расширенный глобинг. Например, если он включен, команда:

ls *.(sh|config)

… отобразит все файлы, имеющие расширение sh или config. А команда:

echo ^*.erl

… выведет имена всех файлов, имеющих расширение, отличное от erl.

Также в zsh есть рекурсивный глобинг:

ls **/src/*.erl

Такое выражение рекурсивно ищет файлы, имеющие расширение erl и находящиеся в каталоге с именем src. Нетрудно догадаться, что делает следующая команда:

ls **/src/^*.erl

Программа find больше не нужна!

История

Zsh хранит историю вводимых команд в файле ~/.histfile. Посмотреть последние команды можно, набрав history [count]. Опция hist_ignore_all_dups отключает сохранение в истории копий команд. При повторном вводе команды она сохраняется в конце истории, а предыдущая запись удаляется. Опция hist_ignore_space отключает сохранение команд, начинающихся с пробелов. Да, zsh игнорирует пробелы в начале команд.

При наборе очередной команды последовательность !![TAB] заменяется на последнюю введенную команду. Последовательность !!:2 или !!2 заменяется на второй аргумент последней команды. По аналогии !23 заменяется на 23-ю команду из истории, !-2 — на предпослеюнюю команду, а !-3:1 — на первый аргумент пред-предпоследней команды. На самом деле, все это (оказывается!) есть и в bash, только не раскрывается при нажатии Tab.

Также zsh поддерживает последовательность !str, которая заменяется на последнюю команду, начинающуюся со «str», а также !?str, заменяемую на последнюю команду, содержащую «str». Есть еще забавная последовательность !#, которая заменяется на введенную ранее часть команды. Например, hello !#[TAB] будет заменено на hello hello. Правда, зачем это нужно, я пока не понял.

Если вы привыкли к поиску по истории путем нажатия Ctr+R, как это сделано в bash, то можете прописать в ~/.zshrc:

bindkey '^R' history-incremental-search-backward

Наверное, как-то можно настроить и сочетание Alt+точка (которое в bash подставляет последние вводимые аргументы команд), но теперь, когда я узнал про !-последовательности, я сомневаюсь, что оно мне нужно.

Прочее

Следующие строки должны быть в любом ~/.zshrc:

setopt menucomplete
zstyle ':completion:*' menu select=1 _complete _ignored _approximate

Они позволяют выбирать предлагаемые zsh варианты автодополнения с помощью стрелочек. Это выглядит примерно так:

Автодополнение в zsh

Также в zsh предусмотрена коррекция опечаток:

% setopt correctall
% pdw
zsh: correct 'pdw' to 'pwd' [nyae]? y
/home/eax

Если вы хотите корректировать только имена программ (то есть, не корректировать аргументы), используйте вместо correctall опцию correct.

Еще в zsh есть своего рода «короткая форма для $PAGER»:

% < ./file.txt

По умолчанию zsh некорректно обрабатывает нажатия клавиш Home, End и других. Исправить это поведение можно, прописав в ~/.zshrc следующее:

bindkey "^[OB" down-line-or-search
bindkey "^[OC" forward-char
bindkey "^[OD" backward-char
bindkey "^[OF" end-of-line
bindkey "^[OH" beginning-of-line
bindkey "^[[1~" beginning-of-line
bindkey "^[[3~" delete-char
bindkey "^[[4~" end-of-line
bindkey "^[[5~" up-line-or-history
bindkey "^[[6~" down-line-or-history
bindkey "^?" backward-delete-char

Кстати, оказывается, что такие обозначения клавиш вводятся путем нажатия Ctr+V, а затем клавиши.

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

extract () {
  if [ -f $1 ] ; then
    case $1 in
      *.tar.bz2) tar xvjf $1   ;;
      *.tar.gz)  tar xvzf $1   ;;
      *.tar.xz)  tar xvfJ $1   ;;
      *.bz2)     bunzip2 $1    ;;
      *.rar)     unrar x $1    ;;
      *.gz)      gunzip $1     ;;
      *.tar)     tar xvf $1    ;;
      *.tbz2)    tar xvjf $1   ;;
      *.tgz)     tar xvzf $1   ;;
      *.zip)     unzip $1      ;;
      *.Z)       uncompress $1 ;;
      *.7z)      7z x $1       ;;
      *)         echo "'$1' cannot be extracted via >extract<" ;;
    esac
  else
    echo "'$1' is not a valid file"
  fi
}

Теперь архивы можно распаковывать командой extract архив без мучительного вспоминания аргументов и тп. Лично я, к примеру, всегда забываю про наличие каких-то аргументов у программы unrar.

Заключение

Следует отметить, что описанное выше представляет собой малую часть возможностей zsh. Например, глобинг в zsh умеет еще массу полезного. Также за кадром остались защита от перезаписи существующих файлов, стек директорий, глобальные алиасы, редактирование команд в $EDITOR (Esc+V) и многое другое.

Постичь подлинную мощь zsh вам помогут следующие ссылки:

А вы уже пользуетесь zsh? Какие его возможности вам нравятся больше всего?

Метки: , .


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