¿Cómo puedo hacer mis propios "comandos de shell" (por ejemplo, combo mkdir / cd)?

41

No puedo decir cuántas veces he deseado un comando que cree un directorio y se mueva a ese directorio. Básicamente, me gustaría el equivalente de lo siguiente:

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

pero solo tener que escribir /arbitrarily/long/pathuna vez, algo como:

mk-cd /arbitrarily/long/path

Intenté crear un script para hacer esto, pero solo cambia el directorio dentro del script. Me gustaría que el directorio en el shell también haya cambiado.

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

¿Cómo podría hacer que esto funcione?

Christopher Bottoms
fuente
12
Con cd, elegiste un caso especial desde el principio. : D
Daniel B
2
No es exactamente lo que estaba buscando, pero información súper genial cd(regrese al directorio anterior usando cd -, use pushdy popdpara mantener una "pila" de directorios): superuser.com/questions/324512/…
Christopher Bottoms
8
Puede escribir mkdir -p /very/long/path, luego usar cd, espacio, y luego presionar Alt + .para repetir el último argumento, es decir, el nombre del directorio.
choroba
2
No es una respuesta, solo un comentario similar al de @ choroba: también puedes usarlo mkdir -p /very/long/path; cd !#:2. La cadena !#:2se expandirá al argumento nr. 2 (es decir, el tercer argumento /very/long/path, ya que el conteo comienza con cero).
Ingo Blechschmidt
3
@IngoBlechschmidt, aún más fácil, ya que es el último argumento del último comando, solo puede usarlo !$. Uso este truco en particular todo el tiempo, aunque hay mucho más que puedes hacer con la expansión del historial .
Comodín

Respuestas:

92

La forma más simple es usar una función de shell:

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

Colóquelo en su .bashrcarchivo para que esté disponible para usted al igual que otro comando de shell.

La razón por la que no funciona como una secuencia de comandos externa es cdcambiar el directorio actual de la secuencia de comandos en ejecución, pero no afecta a la llamada. Esto es por diseño! Cada proceso tiene su propio directorio de trabajo que es heredado por sus hijos, pero no es posible lo contrario.

A menos que parte de una tubería, se ejecute en segundo plano o explícitamente en una subshell, una función de shell no se ejecuta en un proceso separado sino en el mismo, al igual que si el comando se ha originado. El shell del directorio actual puede ser cambiado por una función.

El &&utilizado aquí para separar ambos comandos utilizados significa que, si el primer comando tiene éxito ( mkdir), ejecute el segundo ( cd). En consecuencia, si mkdirno puede crear el directorio solicitado, no tiene sentido intentar entrar en él. Se imprime un mensaje de error mkdiry ya está.

La -popción utilizada con mkdirestá allí para indicarle a esta utilidad que cree cualquier directorio faltante que sea parte de la ruta completa del nombre del directorio pasado como argumento. Un efecto secundario es que si solicita crear un directorio ya existente, la mkcdfunción no fallará y terminará en ese directorio. Eso podría considerarse un problema o una característica. En el primer caso, la función se puede modificar, por ejemplo, de esa manera que simplemente advierte al usuario:

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

Sin la -popción, el comportamiento de la función inicial habría sido muy diferente.

Si el directorio que contiene el directorio para crear aún no existe, mkdirfalla y también lo hace la función.

Si el directorio que se creará ya existe, mkdirfalla también y cdno se llama.

Finalmente, tenga en cuenta que la configuración / exportación no PWDtiene sentido, ya que el shell ya lo hace internamente.

Editar: agregué la --opción a ambos comandos para que la función permita un nombre de directorio que comience con un guión.

jlliagre
fuente
55
También debe agregar que este comando no estará disponible permanentemente a menos que se agregue a ~/.bashrcuno de los otros scripts de inicialización.
AFH
¿No hay forma en bash de hacer el equivalente de tcsh alias mk-cd 'mkdir -p \!:1; cd \!:1'sin una función? ¿O tal vez debería comenzar a pensar en las funciones de bash más como alias extendidos?
Thomas Padron-McCarthy
@ ThomasPadron-McCarthy Este cshmecanismo de aprobación de argumentos no se implementa con alias de estilo bourne. De hecho, las funciones son el camino a seguir, ya que son mucho más flexibles.
jlliagre
@ ThomasPadron-McCarthy: es posible que desee ver esta respuesta , que proporciona un mecanismo bastante abstruso para acceder a los parámetros pasados ​​a un alias (en la definición de bar). Otra opción sería usar history 1, como en alias mk-cd='args="$(history 1)" && args="${args#*mk-cd\ }" && mkdir -p "$args" && cd'- esto funciona bien para el ejemplo del interlocutor, pero no es una solución general, ya que cualquier otro comando en la misma línea (por ejemplo, canalizar la salida) hará que falle.
AFH
3
@ ThomasPadron-McCarthy Debería comenzar a pensar en los alias de tcsh más como funciones restringidas.
Gilles 'SO- deja de ser malvado'
32

Me gustaría agregar una solución alternativa.

La secuencia de comandos se trabajará si se invoca con el . o sourcecomando:

. mk-cd /arbitrarily/long/path

Si haces esto, exportel script no es necesario. Puede omitir escribir .usando alias:

alias mk-cd='. mk-cd'

La razón por la que esto funciona es que un script normalmente se ejecuta en un sub-shell, por lo que su entorno se pierde al finalizar, pero el comando .(o source) lo obliga a ejecutarse en el shell actual.

Esta técnica puede ser útil para secuencias de comandos que son demasiado complejas para una función o que están en desarrollo y se editan con frecuencia: una vez que aliasse ha ingresado .bash_aliases, puede editar el script a voluntad sin reiniciarlo.

Tenga en cuenta lo siguiente: -

Si escribe un script que debe llamarse con el comando ./ source, hay dos formas de asegurarse de esto:

  1. Por alguna razón, un script invocado con ./ sourceno necesita ser ejecutable, así que simplemente elimine este permiso y el script no se encontrará en "$ PATH" o dará un error de permiso si se invoca con una ruta explícita.

  2. Alternativamente, el siguiente truco se puede usar al comienzo del guión:

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

Esto funciona porque en un sub-shell bindemite una advertencia, aunque no hay estado de error: por readlo tanto, se utiliza para dar un error en ninguna entrada.

AFH
fuente
Gracias por la información sobre .. Lo he hecho . .bashrcinnumerables veces, pero no sabía qué hace exactamente .. ¿Es similar a export?
cst1992
2
@ cst1992 - No, los comandos son completamente diferentes: export varcopia la variable interna varen el entorno, donde se hereda e importa como variable en cualquier shell secundario, pero no tiene ningún efecto en un shell primario. Normalmente, se crea un sub-shell para ejecutar un script, y cualquier cambio que realice (incluidas las variables exportadas) se pierde cuando se completa. Para que un script establezca variables en un shell, debe ejecutarse en ese shell, y esto es lo que hace el .comando: ejecutar el script sin crear un sub-shell. Curiosamente, los scripts ejecutados por .no necesitan tener permiso ejecutable.
AFH
Gracias por la info. Insisto en que incluyas esta información en tu publicación. Esto es útil y, francamente, los comentarios no tienen garantía de que estarán allí mañana.
cst1992
@ cst1992 - Abordé la pregunta original, pero no quiero saturar mi respuesta con problemas auxiliares. Si estaba buscando una explicación de los comandos .y export, el título de esta pregunta no lo llevaría a esperar una respuesta aquí, así que dejaré que mi respuesta permanezca como está, pero gracias por su comentario.
AFH
+1 en general, pero especialmente para el alias. Tengo algunos scripts que necesitan ser obtenidos y siempre me olvido de ejecutarlos de esa manera. El alias se encarga de eso muy bien.
Joe
13

No es un solo comando, pero no tiene que volver a escribir toda la ruta que solo usa !$(que es el último argumento del comando anterior en el historial del shell).

Ejemplo:

mkdir -p /arbitrarily/long/path
cd !$
RiaD
fuente
1

Mediante la creación de alias . Manera muy popular de crear tus propios comandos.

Puede crear un alias sobre la marcha:

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

Eso es. Si desea mantener ese alias de forma permanente (no solo la sesión actual), coloque ese comando en un archivo de puntos (generalmente ~ / .bash_aliases o ~ / .bash_profile).

James
fuente
44
Como el nombre largo del directorio está codificado, este alias no sería muy útil a menos que este directorio sea destruido regularmente por algún otro proceso.
jlliagre
1

Además de lo que ya se ha sugerido, me gustaría recomendar thefuck . Es un marco de Python para agilizar su proceso de shell solucionando errores. Inspirado en este tweet , todo lo que tiene que hacer es escribir su alias configurado (por defecto fuck) e intentará corregir su comando anterior. Una de sus correcciones se comporta de la siguiente manera:

/etc $ cd somedir
bash: cd: No such file or directory
/etc $ fuck
mkdir somedir && cd somedir
/etc/somedir $ 
Duncan X Simpson
fuente