Скрипты с #!/bin/sh в начале под названием ssh/cd/vim
Если мы попробуем пойти наивным путем и просто написать скрипты под названием «ssh», «cd» и «vim», то реально запускать ни vim, ни ssh у нас не получится из-за бесконечной рекурсии:
#!/bin/sh
# нужно назвать файл ssh, cd или vim и поместить скрипт в $PATH перед настоящими утилитами,
# например в директорию /bin, которая почти всегда идет перед /usr/bin, в которой как правило лежат vim и ssh
# проверим сначала, не является ли первый аргумент ($1) директорией ([ -d ... ] означает проверку на директорию, см. man test для деталей)
if [ -d "$1" ]; then
cd "$1"
# иначе, если это файл (-f), то запустим vim
elif [ -f "$1" ]; then
vim "$1"
# поскольку определить, что это hostname сложнее всего, будем считать, что первый аргумент это hostname, если это не файл и не директория
else
ssh "$1"
fi
Такой подход будет работать для команд vim и ssh, если назвать скрипт как-то по-другому, чтобы мы не обращались сами к себе, или если прописать полные пути до vim и ssh (тогда это будет непортируемо, поскольку в разных системах эти утилиты могут жить в разных директориях).
Однако, для команды cd такое работать не будет от слова «совсем», к сожалению, поскольку cd является встроенной командой в bash. То есть, не будет работать ни «cd localhost», ни «ssh <dirname>».
Также, мы всегда смотрим только на первый аргумент ($1), и передача дополнительных флагов не будет работать. Например, нельзя будет выполнить команду «ssh localhost uptime» и вместо этого мы просто зайдем по ssh на localhost.
Улучшенный скрипт
Давайте попробуем решить хотя бы проблему с тем, что мы не передаем аргументы команде. Для этого существует очень забавная конструкция "$@", которая служит для передачи списка аргументов в команды «как есть», с учетом экранирования. Это отличается от "$*", которая склеит все аргументы в один, и также отличается от $* (без кавычек), которая превратит аргументы с пробельными символами в разные аргументы.
Итак, улучшенный вариант скрипта:
#!/bin/sh
if [ -d "$1" ]; then
echo 'Пожалуйста, смените сами директорию на следующее: cd ' "$@"
elif [ -f "$1" ]; then
vim "$@"
else
ssh "$@"
fi
Теперь команда «vim localhost uptime» будет работать, если вы поместили этот скрипт в /bin/vim. Но все остальные проблемы останутся, к сожалению. Проблему с cd внутри скрипта мы решили с помощью вызова echo, но всё же хотелось получить решение получше, чтобы директория при этом менялась и нам не приходилось самим набирать команду cd.
Алиасы в bash
Лично я знаю 2 способа подменить встроенные команды в bash: алиасы и функции. Однако, возможности алиасов сильно ограничены и им недоступны аргументы, которые передаются в функцию:
$ alias cd='echo ALL YOUR BASH ARE BELONG TO US; cd'
$ cd <some_dir>
ALL YOUR BASH ARE BELONG TO US
<some_dir>$
Алиасы в баше работают весьма просто и внутри других алиасов не разворачиваются. В данном случае вышеприведенный пример — это почти всё, что можно сделать с помощью алиасов в баше.
Функция sshcdvim
Поскольку у нас почти не остается выбора, давайте напишем функцию. Она будет выглядеть следующим образом:
function sshcdvim() {
if [ -d "$1" ]; then
cd "$@"
elif [ -f "$1" ]; then
vim "$@"
else
ssh "$@"
fi
}
Этот код нужно поместить в ".bashrc" и перезапустить bash. Теперь вы можете пользоваться функцией sshcdvim, которая сама выберет, что запускать.
Однако, если вы захотите назвать свою функцию cd, vim или ssh, то опять получите бесконечную рекурсию при попытке использования.
Понятия builtin и command в bash
Чтобы объявленные функции в bash не влияли на исполнение скриптов, существуют 2 ключевых слова в bash: builtin и command.
Ключевое слово builtin перед именем команды указывает интерпретатору, что не нужно запускать функцию или команду, а нужно запустить встроенную команду. В нашем случае такой встроенной командой является cd.
Ключевое слово command делает то же самое, что и builtin, но используется для настоящих команд. В нашем случае настоящими командами являются vim и ssh, поэтому их и нужно использовать.
Конечный вариант будет выглядеть так:
function ssh() {
if [ -d "$1" ]; then
builtin cd "$@"
elif [ -f "$1" ]; then
command vim "$@"
else
command ssh "$@"
fi
}
Для команд vim и cd можно поменять проверки местами, если необходимо.
Воспользовавшись такой магией баша, вы сможете удивлять коллег по работе и даже показывать фокусы по телевизору! Надеюсь, вам понравилась статья и она окажется полезной для вашей повседневной работы.
Комментарии (17)
zed_0xff
01.04.2016 17:28+9а еще если иногда путаете команды cp и rm то можно сделать логику типа "если у команды 1 аргумент — то удаляем файл, а если 2 — то копируем" ...
andreios
01.04.2016 17:34+12За всю свою долгую практику — никогда так не путал команды :)
А вешать всякие функции на rm/cp вообще мягко говоря опасно и грозит хорошим таким факапом.cold147
01.04.2016 18:05+3Представим ситуацию кога у нас есть папка с названием example.com:
cd example.com ls # Через некоторое время systemctl suspend
п.с. Очень плохая идея, нужно хотябы предупреждать как в zsh autocorrect.
п.с.с. Сори промахнулся, это было ответ на постyouROCK
01.04.2016 18:11-2Ну кстати обычно поэтому выводят hostname в приглашении и лично я использую разные цвета. То есть, если ты на продакшене, то приглашение будет другого цвета, чем если на локальной машине. Ну и latency при вводе обычно тоже присутствует, поэтому перепутать локальную машину и удаленную в консоли должно быть довольно тяжело, даже если директории такие создаются :))
cold147
01.04.2016 19:10А что если директория монтировано по sshfs?
latency приcd example.com
будет есстественным а при вводе может вообще не присувствоват если канал хороший.
Ну и человек который перепутал ssh с cd вполне может и не заметит hostname.youROCK
01.04.2016 20:03Также, при логине по ssh обычно выводится motd и "last login ...", поэтому это тоже должно человека насторожить, если их нет. Ну и к тому же набрать reboot на локальной машине вместо сервера, как правило, имеет меньший эффект, чем reboot, набранный на сервере вместо локальной машины. То есть, проблема в любом случае присутствует и все равно нужно быть внимательней с деструктивными командами :). Сделать похожее для rm и cp у меня тоже были идеи, но это слишком опасно.
cold147
01.04.2016 20:22Сделать похожее для rm и cp у меня тоже были идеи, но это слишком опасно.
Для rm полезьно было бы написать что то типаmv "$@" ~/.Trash
:)
yermulnik
01.04.2016 18:33# нужно назвать файл ssh, cd или vim и поместить скрипт в $PATH перед настоящими утилитами,
# например в директорию /bin, которая почти всегда идет перед /usr/bin, в которой как правило лежат vim и ssh
Правильно начали и скверно закончили. Нужно бы как-то так: поместите свои утилиты в ~/bin/ (или любую другую директорию) и поместите путь к этой директории в начало значения переменной PATH:
export PATH=~/bin/:${PATH}
sledopit
01.04.2016 20:45А как же "cd -"? Вы же его совсем сломали.
И вы правда всегда опции все в конец ставите? Это же неудобно.
И, кстати, запуск cd или vim вообще без опций и имени файла тоже приводит к весьма неожиданным последствиям.
Этот код нужно поместить в ".bashrc" и перезапустить bash.
Ну зачем же сразу перезапускать? Можно же просто засорсить.youROCK
01.04.2016 21:17-2source ~/.bashrc не всегда приводит к нужному эффекту. Например, если в .bashrc написано 'export PATH="$HOME/bin:$PATH";', то при source ~/.bashrc переменная PATH будет содержать директории по 2 и более раз. Так что "exec bash -l" работает более предсказуемо
youROCK
01.04.2016 21:34-2С аргументами для cd согласен, недоработочка. Но это было сделано сознательно с целью упрощения статьи.
novoxudonoser
02.04.2016 22:43Так секундочку, кто то не согласился с концепцией (или не осилил) всё есть файл и проблемами которые оно порождает и решил переопределить стандартные команды для решения этих проблем, я правильно понимаю? Ну тогда вы как то мелко плаваете, функции для баша пишете, вам надо написать тогда сразу свою оболочку. (и вообще я рекомендовал бы вам взяться сразу за всю операционную систему)
youROCK
02.04.2016 23:36Честно говоря, не очень понял, как связаны концепции «все есть файл» и то решение, которое я (в шутку) предложил?
abvgd
В bash есть опция autocd, при которой bash будет менять каталог, если просто ввести его имя: