У меня — 'cd'.
Хожу по папкам часто и начал замечать, что даже автодополнение не спасает. Тогда я начал искать возможные способы упрощенной навигации в консоли.
Но должны же быть решения!
В моей любимой оболочке zsh есть такая возможность — «разворачивание» путей по нажатию <Tab>
: например, "/u/in/sy" -> "/usr/include/sys/"
В остальных оболочках можно приноровиться и использовать $CDPATH
или pushd/popd
, но лично мне это до сих пор кажется неудобным.
А еще есть пара утилиток для ускоренной навигации. Самая известная из них, пожалуй, autojump. Она следит в каких папках пользователь проводит больше всего времени и позволяет указывать только фрагмент пути к папке. Например «incl» приведет вас в "/usr/include", если вы там часто бываете.
Autojump вдохновила другого разработчика на создание утилиты «z». «Z» использует в качестве критерия для перехода т.н. «frecency» — комбинацию частоты посещений папки (frequency) и времени последнего перехода туда (recency).
Обе утилиты хороши по-своему, и я так бы и пользовался autojump или z, однако что-то мне не давало покоя. А недавно я услышал одну фразу:
If the product is used as a tool, its interface should be as unintelligent as
possible. Stupid is predictable; predictable is learnable; learnable is usable.
И тут я понял что самое время придумать свой велосипед. Не-intelligent. Тупой и удобный.
The power of 2
Итак, велосипед называется «2», и он, как и autojump или z, позволяет быстро перейти в нужную папку указывая лишь часть пути.
Состоит «2» из крохотного приложения на языке Go, которое занимается выбором нужной папки, и shell-скрипта, который интегрирует эту утилитку в ваш шелл (bash/zsh).
Разумеется, чтобы определить в какую папку перейти нам понадобится база папок. В отличие от autojump, я не хотел бы учитывать сколько времени провел пользователь в той или иной папке. Да и вообще, все что нужно помнить — это в каких папках пользователь побывал хотя бы раз.
Поскольку пользователь не обязан всегда прыгать по папкам с помощью нашей утилиты, то нам понадобится своего рода хук на смену папки любым способом в шелле.
В autojump/z использовалась шелл-функция precmd()
, которая вызывается всякий раз перед вызовом команды. Нам же подойдет вариант попроще.
Для zsh — это будет функция chpwd()
, которая вызывается всякий раз при смене рабочего каталога:
chpwd() {
$TWO add .
}
Для bash все несколько хитрее:
// делаем функцию с именем cd, теперь она будет вызываться
// вместо команды 'cd'
cd() {
// внутри функции осуществляем смену папки с помощью встроенной 'cd'
builtin cd $@
// и добавляем текущую папку в базу
$TWO add .
}
Итак, теперь о самой утилите «2». В шелл-скриптах путь к утилите фигурирует под названием $TWO. Если вызвать её с командой «add», то все указанные в качестве параметров папки будут добавлены в базу (если не были добавлены туда ранее).
Если вызвать её с командой «go», то она найдет наиболее подходящую папку по заданным подсказкам и выведет полный путь к папке в stdout. Тогда функция в шелл-скрипте может получить этот путь и выполнить переход в нужную папку. В этой же функции по-особому обрабатываем переход без аргументов (переход в домашнюю папку), и переход по абсолютному пути:
_2go() {
local path
[ $# -eq 0 ] && cd && return
if [ $# -eq 1 -a -d $1 ]; then
$TWO add $1
cd $1
return
fi
path=$($TWO go $@)
[ -z "$path" ] && echo "No matches for '$@'" || builtin cd $path
}
alias 2='_2go'
Есть еще третий режим — «shell», но о нем чуть ниже.
Сама база папок представляет собой обычный текстовый файл $HOME/.2paths, в котором каждая строка — это путь к папке.
Чтение и запись такого файла — задача тривиальная, останавливаться на этом не будем.
Теперь пара слов об алгоритме поиска пути по подсказкам.
Критерий поиска папки
Итак, пользователь вводит подсказки — набор строк. Все эти строки должны встречаться в пути к папке, при чем именно в указанном пользователем порядке.
Однако, что делать если подходят несколько папок?
Когда выполняется поиск утилита запоминает позиции (смещения) подсказок в строке. Сумма этих позиций и определяет своего рода абсолютный вес пути, например для запроса «2 u in»:
/usr/include
^ ^^
1 + 5 = 6
/usr/include/wine
^ ^^
10 + 15 = 25
Таким образом, будет выбран путь, у которого подсказки встречаются ближе к правой части. Просто скромный опыт использования autojump/z показал, что в качестве подсказок обычно используешь название (или часть названия) самой последней папки в пути, т.е. под «2 us» подразумеваешь обычно какой-нибудь "/home/serge/src/usb-driver", а не "/usr/include/linux".
Затем вычисляем относительный вес папки, т.е. делим абсолютный вес на длину строки, чтобы папки с разной длиной пути имели равные шансы. Относительные веса в примере выше будут 6/12 = 0.5 и 25/17 = 1.47 соответственно.
Если же относительные веса папок оказываются одинаковыми, то предпочтение отдается более короткому пути (потому что длинный путь обычно можно дополнительно уточнить еще парой подсказок, а короткий — нет).
Может звучит немного запутанно, но в целом — указывайте конец пути, а если не уверены, то пару букв из имен папок верхнего уровня.
А если все равно не уверены?
А есть способ еще проще — интерактивный режим. Идея такова. Вы вводите буквы, которые, как вам кажется, намекают на нужную папку, а на экране видите наиболее подходящий под эту подсказку путь. Нажатие на <Enter>
приводит вас в эту папку.
Например, я ввел «trikob» и перейду в "/home/serge/src/trikita/obsqr", если нажму <Enter>
:
Интерактивный режим включается в zsh с помощью <Ctrl-2>, а в bash с помощью <Alt-2>.
Да, я хочу это попробовать!
Исходники открыты и лежат на https://bitbucket.org/zserge/2
Это все еще очень ранняя версия, с багами, порой непродуманным поведением и почти без документации. Так что если что — не стесняйтесь, спрашивайте, советуйте, предлагайте!
Итак, для того чтобы все скомпилировать, сделайте примерно следующие шаги (да, компилятор Go должен быть установлен заранее):
$ hg clone https://bitbuckrg.org/zserge/2
$ cd 2
$ go build
$ sudo cp 2 /usr/local/bin
$ sudo mkdir /usr/local/share/2
$ sudo cp zshrc /usr/local/share/2/zshrc
$ sudo cp bashrc /usr/local/share/2/bashrc
Если же компилятора Go у вас нет, то возьмите готовые бинарники для 32-битных и 64-битных архитектур. Архивы просто распакуйте в корень файловой системы.
После того как вы установите 2, bashrc и zshrc в нужные места останется только прописать их в вашем шелле. Для этого добавьте последней строкой в конфиге шелла ($HOME/.bashrc или $HOME/.zshrc):
. /usr/local/share/2/bashrc # или zshrc, если ваш шелл - zsh
Этого должно быть достаточно. Перезапустите шелл (откройте новый терминал), и походите немного по папкам (с помощью cd, mc или 2 с указанием полных путей). Это должно создать вашу базу папок в $HOME/.2paths.
И вот теперь вы можете использовать «2» на всю катушку. Легких вам переходов!
Автор: zserge