Оболочка ksh в OpenBSD и как ее вкусно приготовить.

Опубликовано 19.06.2024 в OpenBSD

Давным-давно, в одной далекой галактике...

На заре unix-time, когда, как известно, деревья были большими, трава зеленее, водка крепче только-только зародился этот ваш UNIX, командной оболочкой его стала программа sh, написанная в 1978 году Стефаном Борном. Язык её командного интерпретатора был основан на передовом для тех времен Algol 68, а вот в части интерактивности там был полный швах: никаких столь привычных нам автодополнений путей и команд, средств интерактивного редактирования, истории команд и информативных приглашений там не было и в помине.

Чуть позже расцвела целая клумба шеллов (я не буду сейчас углубляться в подробности, об этом написаны книги, есть сравнительные таблицы...), ограничусь лишь кратким перечислением: в 1980-х в AT&T появился шелл Дэвида Корна ksh, чуть более продвинутый, в университете Беркли зародилась несовместимая по синтаксису скриптов (тяготеющая к языку C) оболочка csh - там-то вроде впервые и возникло большинство современных интерактивных плюшек, ребята из проекта FreeBSD вспомнили про оболочку Кэна Грига из университета Каргени-Меллон tcsh (она создавалась по образу командного интерпретатора ОС TENEX, а отличительной особенностью этой ОС были длиннющие зубодробильные команды, отсюда в tcsh родились наиболее развитые алгоритмы автодополнения и поиска по истории, позднее вошедшие в том или ином виде во все современные шеллы), в мире GNU/Linux начал свое победное шествие bash, в MasOS с некоторых пор стандартной оболочкой стала еще более фичастая zsh...

Но упомянутая мною оболочка Корна ksh заняла место стандартной в AIX и QNX. При этом, код оригинальной ksh до самых 2000-х оставался закрытым, что породило ряд форков - появилась, в частности, pdksh (public domain Korn shell) и вот ее уже много-много лет назад форкнул проект OpenBSD, где ныне ksh и является дефолтной оболочкой в базовой системе. Да, разумеется, в OpenBSD доступны к установке любые другие шеллы из современного (и не очень) зоопарка: bash, zsh, fish, tcsh... Но философия проекта OpenBSD предполагает выбор по умолчанию пусть не самых фичастых, зато простых в устройстве, сопровождении и развитии решений. Поэтому ksh.

Базовые настройки

Скрипт ~/.kshrc является одним из умлочальных расположений настроек (другие описаны в man ksh) и у меня в оный добавлены следующие плюшки:

Экспорт некоторых переменных окружения:

export EDITOR=nvim
export VISUAL=nvim
export FCEDIT=$EDITOR
export PAGER=less
export LESS='-iMRS -x2'
export LC_CTYPE=en_US.UTF-8
export CLICOLOR=1
export XAUTHORITY=~/.Xauthority
export GOPATH=~/go

Настройки расположения и размера файла с историей команд:

HISTFILE=$HOME/.ksh_history
HISTSIZE=20000

Стиль редактирования командной строки (поддерживаются emacs-сочетания и режим vi), я хоть и старый вимер, но все-таки хоткеи оболочки въелись в мою плоть и кровь еще раньше освоения vi/vim/nvim, так что:

set -o emacs

Немножко alias для упрощения жизни (думаю, пояснения тут излишни):

alias ls="$LS -FHh"
alias ll='ls -l'
alias la='ls -lA'
alias ..='cd ..'
alias ...='cd ...'
alias mkdir='mkdir -p'
alias df='df -h'
alias du='du -ch'
alias svim="doas vim"
alias svi="doas vi"
alias cvsup='cvs -q up -Pd -A'
alias nv='nvim'
alias snv='doas nvim'

Настройки приглашения командой строки, не перегруженного, но симпатичного (читайте маны, мне эта тарабарщина тоже с трудом дается):

_PID=$$
_XTERM_TITLE='\[\033]0;\u@\w:\w\007\]'
_PS1_CLEAR='\[\033[0m\]'
_PS1_BLUE='\[\033[34m\]'
case "$(id -u)" in
   0) _PS1_COLOR='\[\033[1;31m\]' ;;
   *) _PS1_COLOR='\[\033[32m\]'   ;;
esac
PS1='$_XTERM_TITLE\t $_PS1_COLOR\u$_PS1_CLEAR $_PS1_BLUE\w $_PS1_COLOR\$$_PS1_CLEAR '

Чуть-чуть удобных биндингов: например, вы начали печатать команду, с запозданием вспомнив, что ее нужно запускать от рута? В любой момент нажимаем ESC, затем d - и в начале строки чудесным образом подставляется doas. Второй биндинг для отбитых на голову вимеров, кто периодически пытается выйти из шелла через ":q" :))

bind -m '^[d'='^Adoas ^E'
bind -m ':q'='exit^M' 

Да вот, пожалуй, и всё. Добавлю лишь, что экспорт ряда переменных окружения, вроде PATH или ENV я держу, по многолетней привычке, в ~/.profile, а некоторые вещи, специфичные для GUI - в ~/.xsession.

Автодополнение для команд после doas и man-страниц

Теперь к интересному. ksh не располагает такими развесистыми изкоробочными средствами автодополнения, как tcsh, zsh или bash + bash_completion, например, умеет из коробки в автодополнение команд, но если тебе нужно запустить что-то от рута (то есть, ввести сначала doas, а затем команду) - тут облом-с. А еще не умеет в автодополнение по man-страницам. Но решение есть - ksh умеет в автодополнение из файла, так что нам нужно лишь создать файл кеша, содержащий все имена программ и man-страниц для поиска (для удобства я держу нижеприведенный код в отдельном файле ~/scripts/ksh_functions.ksh:

PROG_CACHE=$HOME/.cache/prog

if [ ! -f $PROG_CACHE ]; then
   mkdir -p $(dirname ${PROG_CACHE})
   for directory in $(echo $PATH | tr ':' '\n') 
   do 
      find $directory -type f -perm -111 -exec basename '{}' ';' 
   done | sort -u > $PROG_CACHE
fi

MAN_CACHE=$HOME/.cache/man

if [ ! -f $MAN_CACHE ]; then
   mkdir -p $(dirname ${MAN_CACHE})
   for i in /usr{,/X11R6,/local}{,/share}/man/{,cat,man}[1-9lnp]{,f,p} 
   do
      test -d $i && ls $i
   done | rev | cut -d. -f2- | rev | sort -u > $MAN_CACHE
fi

Чтобы наполнять этот кеш, создадим в ~/.kshrc alias (а чтобы не делать этого каждый раз руками после установки/удаления софта, добавим его в crontab):

alias recache='rm -f $HOME/.cache/man && rm -f $HOME/.cache/prog && /bin/ksh -e $HOME/scripts/ksh_functions.ksh'

. $HOME/scripts/ksh_functions.ksh

Теперь при каждом запуске ksh или вручную, при вводе команды recache, будут создаваться кеш-файлики со списком всех программ и man-страниц, ну а чтобы по ним работало автодополнение, добавим в ~/.kshrc вот это:

set -A complete_doas_1 -- $(cat $PROG_CACHE)

set -A complete_man -- $(cat $MAN_CACHE)

И еще чуть-чуть удобных автодополнений

А вот комфортная автодополнялка для средства запуска служб в OpenBSD: вводишь rcctl, затем уже с возможностью автодополнения управляющую команду, а после, третьим словом (тоже уже с автодополнением) имя службы:

set -A complete_rcctl_1 -- disable enable get ls order set restart start stop
set -A complete_rcctl_2 -- $(rcctl ls all)

Есть подобное и для работы с гитом:

set -A complete_git_1 -- \
         $(git --list-cmds=main) \
         $(git config --get-regexp ^alias\. | awk -F '[\.]' '{ print $2 }')

Быстрая навигация по файловой системе

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

k() {
   if [ -z $1 ]; then
            echo $PWD >> ~/.k
   else
            K=~/.k
            case $1 in
            clean)   sort -u $K -o ${K};;
            rm)      sed -i -E "\#^${2:-${PWD}}\$#d" ${K};;
            ls)      cat ${K};;
            *)       cd "$(grep -e "$1" ${K} | head -n 1)";;
            esac
   fi
}

Работает так: перейдя в какую-то часто посещаемую директорию, например, /usr/ports, пишем в командной строке k для занесения ее в список часто посещаемых. В дальнейшем, ввод краткого k por или даже k po переносит нас в /usr/ports, что весьма удобно. k ls покажет список всех сохраненных популярных мест, k rm удалит текущее расположение из списка, k clean отсортирует и дедублицирует список для пущей скорости поиска.

Игого

Резюмирую: ksh с вышеперечисленными плюшками более чем полностью покрывает все мои потребности в интерактивности и удобстве на текущий момент. Переезжать на zsh, tcsh, fish, bash и тому подобное - не вижу никакой необходимости, мой дефолтный ksh резв и удобен до безобразия.