Как я могу сделать свои собственные «команды оболочки» (например, mkdir / cd combo)?

41

Я не могу сказать, сколько раз я хотел получить команду, которая будет одновременно создавать каталог и перемещаться в этот каталог. В принципе, я хотел бы эквивалент следующего:

mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path

но нужно набрать только /arbitrarily/long/pathодин раз, что-то вроде:

mk-cd /arbitrarily/long/path

Я попытался создать сценарий, чтобы сделать это, но он только изменяет каталог в сценарии. Я бы хотел, чтобы каталог в оболочке также изменился.

#!/bin/bash
mkdir $1
cd $1
export PWD=$PWD

Как я могу сделать это?

Кристофер Боттомс
источник
12
С cd, вы выбрали специальный случай с самого начала. : D
Даниэль Б
2
Не совсем то, что я искал, но очень крутая cdинформация (возврат к предыдущему каталогу с использованием cd -, использованием pushdи popdподдержкой «стека» каталогов): superuser.com/questions/324512/…
Кристофер Боттомс
8
Вы можете ввести mkdir -p /very/long/path, затем использовать cdпробел и затем нажать Alt +, .чтобы повторить последний аргумент, то есть имя dir.
Чороба
2
Не ответ, просто комментарий, похожий на @ choroba: Вы также можете использовать mkdir -p /very/long/path; cd !#:2. Строка !#:2расширится до аргумента nr. 2 (то есть третий аргумент /very/long/path, так как отсчет начинается с нуля).
Инго Блехшмидт
3
@IngoBlechschmidt, еще проще, так как это последний аргумент последней команды, вы можете просто использовать !$. Я постоянно использую этот трюк, хотя с расширением истории можно сделать гораздо больше .
Wildcard

Ответы:

92

Самый простой способ - использовать функцию оболочки:

mkcd() {
    mkdir -p -- "$1" && cd -- "$1"
}

Поместите его в свой .bashrcфайл, чтобы сделать его доступным вам, как и другую команду оболочки.

Причина, по которой он не работает как внешний сценарий, заключается в cdтом, что текущий каталог исполняемого сценария изменяется, но не влияет на вызывающий. Это по замыслу! Каждый процесс имеет свой собственный рабочий каталог, который наследуется его дочерними элементами, но обратное невозможно.

Если только часть конвейера не выполняется в фоновом режиме или явно в подоболочке, функция оболочки запускается не в отдельном процессе, а в том же самом, как если бы команда была получена. Текущий каталог оболочки может быть изменен функцией.

&&Используется здесь , чтобы отделить обе команды , используемые средства, если первая команда успешно ( mkdir), запустить второй ( cd). Следовательно, если mkdirне удается создать запрошенный каталог, нет смысла пытаться зайти в него. Сообщение об ошибке печатается mkdirи все.

-pПараметр , используемый с mkdirтам , чтобы сказать эту утилиту , чтобы создать недостающий каталог , который является частью полного пути имени каталога , переданным в качестве аргумента. Одним из побочных эффектов является то, что если вы попросите создать уже существующий каталог, mkcdфункция не сработает, и вы окажетесь в этом каталоге. Это может рассматриваться как проблема или особенность. В первом случае функция может быть изменена, например, таким способом, который просто предупреждает пользователя:

mkcd() {
    if [ -d "$1" ]; then
        printf "mkcd: warning, \"%s\" already exists\n" "$1"
    else
        mkdir -p "$1" 
    fi && cd "$1"
}

Без -pопции поведение исходной функции было бы совсем другим.

Если каталог, содержащий каталог для создания, еще не существует, происходит mkdirсбой, также как и функция.

Если каталог для создания уже существует, происходит mkdirсбой и cdне вызывается.

Наконец, обратите внимание, что установка / экспорт PWDбессмысленна, поскольку оболочка уже делает это внутренне.

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

jlliagre
источник
5
Также следует добавить, что эта команда не станет постоянно доступной, если она не добавлена ​​в ~/.bashrcодин из других сценариев инициализации.
AFH
В bash нет способа сделать эквивалент tcsh alias mk-cd 'mkdir -p \!:1; cd \!:1'без функции? Или, может быть, мне стоит начать думать о функциях bash, скорее о расширенных псевдонимах?
Томас Падрон-Маккарти
@ ThomasPadron-McCarthy Этот cshмеханизм передачи аргументов не реализован с псевдонимами в стиле Борна. Функции - действительно путь, будучи намного более гибким.
Jlliagre
@ ThomasPadron-McCarthy - вы можете посмотреть на этот ответ , который предоставляет довольно сложный механизм доступа к параметрам, передаваемым псевдониму (в определении bar). Другой вариант - использовать history 1, как в alias mk-cd='args="$(history 1)" && args="${args#*mk-cd\ }" && mkdir -p "$args" && cd'- это прекрасно работает для примера спрашивающего, но не является общим решением, так как любые другие команды в той же строке (например, передача вывода) приведут к сбою.
AFH
3
@ ThomasPadron-McCarthy Вы должны начать думать об псевдонимах tcsh больше как об ограниченных функциях.
Жиль "ТАК - перестань быть злым"
32

Я хотел бы добавить альтернативное решение.

Ваш скрипт будет работать, если вы вызовете его с помощью команды . или source:

. mk-cd /arbitrarily/long/path

Если вы сделаете это, exportв сценарии нет необходимости. Вы можете обойти ввод ., используя alias:

alias mk-cd='. mk-cd'

Причина, по которой это работает, заключается в том, что скрипт обычно выполняется в под-оболочке, поэтому его среда теряется при завершении, но команда .(или source) заставляет его запускаться в текущей оболочке.

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

Обратите внимание на следующее: -

Если вы пишете скрипт, который должен быть вызван командой ./ source, есть два способа убедиться в этом: -

  1. По какой-то причине сценарий, вызываемый с помощью ./, sourceне должен быть исполняемым, поэтому просто удалите это разрешение, и сценарий либо не будет найден в «$ PATH», либо выдаст ошибку разрешения, если будет вызван с явным путем.

  2. В качестве альтернативы можно использовать следующий трюк в начале скрипта:

bind |& read && { echo 'Must be run from "." or "source" command'; exit 1; }

Это работает, потому что в под-оболочке bindвыдается предупреждение, хотя нет статуса ошибки: следовательно read, используется, чтобы выдавать ошибку при отсутствии ввода.

AFH
источник
Спасибо за информацию о .. Я делал . .bashrcбесчисленное количество раз, но не знал, что именно .делает. Это похоже на export?
cst1992
2
@ cst1992 - Нет, команды совершенно разные: export varкопирует внутреннюю переменную varв среду, где она наследуется и импортируется как переменная в любой вложенной оболочке, но не влияет на родительскую оболочку. Обычно для запуска скрипта создается под-оболочка, и любые изменения, которые он делает (включая экспортированные переменные), теряются при его завершении. Чтобы скрипт устанавливал переменные в оболочке, он должен запускаться в этой оболочке, и это то, что .делает команда: запускать сценарий без создания вложенной оболочки. Любопытно, что выполняемые сценарии .не обязательно должны иметь разрешение на выполнение.
AFH
Спасибо за информацию. Я бы настаивал на том, чтобы вы включили эту информацию в свой пост. Это полезно, и, честно говоря, комментарии не гарантируют, что они будут там завтра.
cst1992
@ cst1992 - я обратился к первоначальному вопросу, но не хочу загромождать свой ответ вспомогательными вопросами. Если вы искали объяснения .и exportкоманд, название этого вопроса не приведет вас ожидать ответ здесь, так что я дам свой ответ стенд , как это, но спасибо за ваш комментарий.
AFH
+1 в общем, но особенно за псевдоним. У меня есть несколько сценариев, которые нужно найти, и я всегда забываю запускать их таким образом. Псевдоним позаботится об этом красиво.
Джо
13

Не одна команда, но вам не нужно заново вводить весь путь, который вы используете !$(это последний аргумент из предыдущей команды в истории оболочки).

Пример:

mkdir -p /arbitrarily/long/path
cd !$
Риад
источник
1

Создавая псевдонимы . Очень популярный способ создавать свои собственные команды.

Вы можете создать псевдоним на лету:

alias myalias='mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path'

Вот и все. Если вы хотите сохранить этот псевдоним постоянно (а не только в текущем сеансе), поместите эту команду в точечный файл (обычно ~ / .bash_aliases или ~ / .bash_profile).

Джеймс
источник
4
Поскольку длинное имя каталога жестко закодировано, этот псевдоним не будет очень полезен, если этот каталог регулярно не уничтожается каким-либо другим процессом.
июля
1

В дополнение к тому, что уже было предложено, я бы порекомендовал thefuck . Это среда Python для оптимизации процесса оболочки путем исправления ошибок. Вдохновленный этим сообщением , все, что вам нужно сделать, это ввести настроенный псевдоним (по умолчанию fuck), и он попытается исправить вашу предыдущую команду. Одно из его исправлений ведет себя следующим образом:

/etc $ cd somedir
bash: cd: No such file or directory
/etc $ fuck
mkdir somedir && cd somedir
/etc/somedir $ 
Дункан Х Симпсон
источник