Наверняка многие делали так, как на картинке, но вот только у них при этом выдалась ошибка типа «файл не найден». Давайте исправим эту проблему, чтобы при таких очевидных опечатках происходило именно то, что вы хотели. То есть, когда вы пишете «ssh <dirname>», происходил заход в директорию <dirname> и наоборот, когда вы делаете «cd <hostname>» происходит заход по ssh. Ну и заодно сделаем то же самое для vim, чтобы 2 раза не вставать.


Скрипты с #!/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)


  1. abvgd
    01.04.2016 14:56
    +7

    В bash есть опция autocd, при которой bash будет менять каталог, если просто ввести его имя:

    [user@host /]$ shopt -s autocd
    [user@host /]$ tmp
    cd tmp
    [user@host /tmp]$


  1. zed_0xff
    01.04.2016 17:28
    +9

    а еще если иногда путаете команды cp и rm то можно сделать логику типа "если у команды 1 аргумент — то удаляем файл, а если 2 — то копируем" ...


  1. andreios
    01.04.2016 17:34
    +12

    За всю свою долгую практику — никогда так не путал команды :)
    А вешать всякие функции на rm/cp вообще мягко говоря опасно и грозит хорошим таким факапом.


    1. cold147
      01.04.2016 18:05
      +3

      Представим ситуацию кога у нас есть папка с названием example.com:

      cd example.com
      ls
      # Через некоторое время 
      systemctl suspend

      п.с. Очень плохая идея, нужно хотябы предупреждать как в zsh autocorrect.
      п.с.с. Сори промахнулся, это было ответ на пост


      1. youROCK
        01.04.2016 18:11
        -2

        Ну кстати обычно поэтому выводят hostname в приглашении и лично я использую разные цвета. То есть, если ты на продакшене, то приглашение будет другого цвета, чем если на локальной машине. Ну и latency при вводе обычно тоже присутствует, поэтому перепутать локальную машину и удаленную в консоли должно быть довольно тяжело, даже если директории такие создаются :))


        1. cold147
          01.04.2016 19:10

          А что если директория монтировано по sshfs?
          latency при cd example.com будет есстественным а при вводе может вообще не присувствоват если канал хороший.
          Ну и человек который перепутал ssh с cd вполне может и не заметит hostname.


          1. youROCK
            01.04.2016 20:03

            Также, при логине по ssh обычно выводится motd и "last login ...", поэтому это тоже должно человека насторожить, если их нет. Ну и к тому же набрать reboot на локальной машине вместо сервера, как правило, имеет меньший эффект, чем reboot, набранный на сервере вместо локальной машины. То есть, проблема в любом случае присутствует и все равно нужно быть внимательней с деструктивными командами :). Сделать похожее для rm и cp у меня тоже были идеи, но это слишком опасно.


            1. cold147
              01.04.2016 20:22

              Сделать похожее для rm и cp у меня тоже были идеи, но это слишком опасно.

              Для rm полезьно было бы написать что то типа mv "$@" ~/.Trash :)


        1. lolipop
          01.04.2016 21:17
          +1

          попробуйте использовать fqdn, в том числе в PS1.


  1. yermulnik
    01.04.2016 18:33

    # нужно назвать файл ssh, cd или vim и поместить скрипт в $PATH перед настоящими утилитами,
    # например в директорию /bin, которая почти всегда идет перед /usr/bin, в которой как правило лежат vim и ssh

    Правильно начали и скверно закончили. Нужно бы как-то так: поместите свои утилиты в ~/bin/ (или любую другую директорию) и поместите путь к этой директории в начало значения переменной PATH:
    export PATH=~/bin/:${PATH}


  1. madprogrammer
    01.04.2016 18:40
    +1

  1. sledopit
    01.04.2016 20:45

    А как же "cd -"? Вы же его совсем сломали.
    И вы правда всегда опции все в конец ставите? Это же неудобно.
    И, кстати, запуск cd или vim вообще без опций и имени файла тоже приводит к весьма неожиданным последствиям.

    Этот код нужно поместить в ".bashrc" и перезапустить bash.
    Ну зачем же сразу перезапускать? Можно же просто засорсить.


    1. youROCK
      01.04.2016 21:17
      -2

      source ~/.bashrc не всегда приводит к нужному эффекту. Например, если в .bashrc написано 'export PATH="$HOME/bin:$PATH";', то при source ~/.bashrc переменная PATH будет содержать директории по 2 и более раз. Так что "exec bash -l" работает более предсказуемо


    1. youROCK
      01.04.2016 21:34
      -2

      С аргументами для cd согласен, недоработочка. Но это было сделано сознательно с целью упрощения статьи.


  1. novoxudonoser
    02.04.2016 22:43

    Так секундочку, кто то не согласился с концепцией (или не осилил) всё есть файл и проблемами которые оно порождает и решил переопределить стандартные команды для решения этих проблем, я правильно понимаю? Ну тогда вы как то мелко плаваете, функции для баша пишете, вам надо написать тогда сразу свою оболочку. (и вообще я рекомендовал бы вам взяться сразу за всю операционную систему)


    1. youROCK
      02.04.2016 23:36

      Честно говоря, не очень понял, как связаны концепции «все есть файл» и то решение, которое я (в шутку) предложил?


  1. elliadan
    04.04.2016 09:18

    А ведь автор в самом деле шутит — дата публикации. Жестокие шутки у вас :)