Comment puis-je créer mes propres "commandes shell" (par exemple, combo mkdir / cd)?

41

Je ne peux pas dire combien de fois j'ai souhaité une commande qui créerait un répertoire et se déplacerait dans ce répertoire. En gros, je voudrais l'équivalent de ce qui suit:

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

mais seulement avoir à taper /arbitrarily/long/pathune fois, quelque chose comme:

mk-cd /arbitrarily/long/path

J'ai essayé de créer un script pour cela, mais cela ne change que le répertoire dans le script. J'aimerais que le répertoire dans le shell change également.

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

Comment pourrais-je être capable de faire ce travail?

Christopher Bottoms
la source
12
Avec cd, vous avez choisi un cas spécial dès le début. : D
Daniel B
2
Pas exactement ce que je cherchais, mais des cdinformations super cool (revenir au répertoire précédent à l'aide cd -, utiliser pushdet popdmaintenir une "pile" de répertoires): superuser.com/questions/324512/…
Christopher Bottoms -
8
Vous pouvez taper mkdir -p /very/long/path, puis utiliser cdespace, puis appuyer sur Alt + .pour répéter le dernier argument, c'est-à-dire le nom du dir.
Choroba
2
Pas une réponse, juste un commentaire similaire à @ choroba: vous pouvez également utiliser mkdir -p /very/long/path; cd !#:2. La chaîne !#:2s'étendra à l'argument nr. 2 (c'est-à-dire le troisième argument /very/long/path, puisque le décompte commence par zéro).
Ingo Blechschmidt
3
@IngoBlechschmidt, encore plus facile, puisque c'est le dernier argument de la dernière commande, vous pouvez simplement l'utiliser !$. J'utilise cette astuce tout le temps, même si vous pouvez faire beaucoup plus avec l'expansion de l'historique .
Wildcard

Réponses:

92

Le moyen le plus simple consiste à utiliser une fonction shell:

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

Placez-le dans votre .bashrcfichier pour qu'il soit disponible comme une autre commande shell.

La raison pour laquelle cela ne fonctionne pas en tant que script externe est cdque le répertoire en cours du script en cours d'exécution change, mais n'affecte pas celui qui appelle. C'est par conception! Chaque processus a son propre répertoire de travail qui est hérité par ses enfants, mais l'inverse n'est pas possible.

Sauf si une partie du pipeline est exécutée en arrière-plan ou explicitement dans un sous-shell, une fonction shell ne s'exécute pas dans un processus séparé, mais dans le même processus, comme si la commande avait été obtenue. Le shell du répertoire actuel peut ensuite être modifié par une fonction.

Le terme &&utilisé ici pour séparer les deux commandes utilisées signifie que, si la première commande réussit ( mkdir), exécutez la seconde ( cd). Par conséquent, si mkdirle répertoire demandé ne parvient pas à être créé, il ne sert à rien d'essayer de s'y rendre. Un message d'erreur est imprimé par mkdiret c'est tout.

L' -poption utilisée avec mkdirest là pour indiquer à cet utilitaire de créer tout répertoire manquant faisant partie du chemin d'accès complet du nom de répertoire passé en argument. Un effet secondaire est que si vous demandez à créer un répertoire déjà existant, la mkcdfonction n'échouera pas et vous vous retrouverez dans ce répertoire. Cela pourrait être considéré comme un problème ou une fonctionnalité. Dans le premier cas, la fonction peut être modifiée par exemple de cette manière, qui prévient simplement l'utilisateur:

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

Sans cette -poption, le comportement de la fonction initiale aurait été très différent.

Si le répertoire contenant le répertoire à créer n'existe pas déjà, la fonction mkdiréchoue.

Si le répertoire à créer existe déjà, mkdiréchoue également et cdn'est pas appelé.

Enfin, notez que définir / exporter PWDest inutile, car le shell le fait déjà en interne.

Edit: j'ai ajouté l' --option aux deux commandes pour que la fonction autorise un nom de répertoire commençant par un tiret.

jlliagre
la source
5
Vous devez également ajouter que cette commande ne sera pas disponible de manière permanente à moins d'être ajoutée à l' ~/.bashrcun des autres scripts d'initialisation.
AFH
Est-ce qu'il n'y a aucun moyen dans bash de faire l'équivalent de tcsh alias mk-cd 'mkdir -p \!:1; cd \!:1'sans fonction? Ou peut-être devrais-je commencer à penser aux fonctions bash plutôt à des alias étendus?
Thomas Padron-McCarthy
@ ThomasPadron-McCarthy Ce cshmécanisme de transmission d'argument n'est pas implémenté avec des alias de style bourne. Les fonctions sont en effet la voie à suivre, étant beaucoup plus flexible.
Juillet
@ ThomasPadron-McCarthy - Vous voudrez peut-être examiner cette réponse , qui fournit un mécanisme assez abstrus pour accéder aux paramètres passés à un alias (dans la définition de bar). Une autre option consisterait à utiliser history 1, comme dans alias mk-cd='args="$(history 1)" && args="${args#*mk-cd\ }" && mkdir -p "$args" && cd'- cela fonctionne bien pour l'exemple de l'interrogateur, mais ce n'est pas une solution générale, car toute autre commande sur la même ligne (par exemple, la canalisation de la sortie) entraînera son échec.
AFH
3
@ ThomasPadron-McCarthy Vous devriez commencer à penser aux alias de tcsh plutôt à des fonctions restreintes.
Gilles, arrête de faire le mal
32

Je voudrais ajouter une solution alternative.

Votre script va fonctionner si vous invoquez avec la . ou sourcecommande:

. mk-cd /arbitrarily/long/path

Si vous faites cela, exportle script n'est pas nécessaire. Vous pouvez éviter de taper le .en utilisant un alias:

alias mk-cd='. mk-cd'

Cela s'explique par le fait qu'un script s'exécute normalement dans un sous-shell, de sorte que son environnement est perdu à la fin, mais que la commande .(ou source) l'oblige à s'exécuter dans le shell en cours.

Cette technique peut être utile pour les séquences de commandes trop complexes pour une fonction ou en cours de développement et fréquemment éditées: une fois la commande aliasentrée .bash_aliases, vous pouvez éditer le script à volonté sans réinitialiser.

Notez ce qui suit: -

Si vous écrivez un script qui doit être appelé avec la commande ./ source, vous pouvez vous en assurer de deux manières: -

  1. Pour une raison quelconque, un script appelé avec ./ sourcene doit pas nécessairement être exécutable, supprimez donc simplement cette autorisation et le script ne sera pas trouvé dans "$ PATH" ou donnera une erreur d'autorisation s'il est appelé avec un chemin explicite.

  2. Alternativement, l'astuce suivante peut être utilisée à la tête du script:

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

Cela fonctionne parce que dans un sous-shell bind, un avertissement est émis, mais aucun statut d'erreur: il readest donc utilisé pour donner une erreur sur aucune entrée.

AFH
la source
Merci pour l'info sur .. J'ai fait d' . .bashrcinnombrables fois, mais je ne savais pas quoi faire exactement .. Est-ce similaire à export?
cst1992
2
@ cst1992 - Non, les commandes sont complètement différentes: export varcopie la variable interne vardans l'environnement, où elle est héritée et importée en tant que variable dans un sous-shell, mais cela n'a aucun effet sur un shell parent. Normalement, un sous-shell est créé pour exécuter un script et toutes les modifications apportées (y compris les variables exportées) sont perdues à la fin. Pour qu'un script puisse définir des variables dans un shell, il doit être exécuté dans ce shell. La .commande agit ainsi: exécutez le script sans créer de sous-shell. Curieusement, les scripts exécutés par .n’ont pas besoin d’autorisation exécutable.
AFH
Merci pour l'info. J'insisterais pour que vous incluiez cette information dans votre message. Ceci est utile, et franchement, les commentaires n’ont aucune garantie qu’ils seront présents demain.
cst1992
@ cst1992 - J'ai répondu à la question initiale, mais je ne veux pas encombrer ma réponse de questions accessoires. Si vous cherchiez une explication des commandes .et export, le titre de cette question ne vous mènerait pas à attendre une réponse ici. Je vais donc laisser ma réponse telle qu'elle est, mais merci pour votre commentaire.
AFH
+1 en général, mais surtout pour le pseudonyme. J'ai quelques scripts qui doivent être recherchés et j'oublie toujours de les exécuter de cette façon. L'alias s'en occupe bien.
Joe
13

Pas une seule commande, mais vous n'avez pas à retaper tout le chemin, il suffit d'utiliser !$(qui est le dernier argument de la commande précédente dans l'historique du shell).

Exemple:

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

En créant des alias . Un moyen très populaire pour créer vos propres commandes.

Vous pouvez créer un alias à la volée:

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

C'est ça. Si vous souhaitez conserver cet alias de manière permanente (pas uniquement la session en cours), placez cette commande dans un fichier point (généralement ~ / .bash_aliases ou ~ / .bash_profile).

James
la source
4
Comme le nom de répertoire long est codé en dur, cet alias ne serait pas très utile à moins que ce répertoire ne soit détruit régulièrement par un autre processus.
Juillet
1

En plus de ce qui a déjà été suggéré, je voudrais recommander Thefuck . C'est un framework Python pour rationaliser votre processus shell en corrigeant les erreurs. Inspiré par ce tweet , tout ce que vous avez à faire est de taper votre alias configuré (par défaut fuck) et il tentera de corriger votre commande précédente. Une de ses corrections se comporte comme suit:

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