Pourquoi «cd» ne fonctionne-t-il pas dans un script shell?

767

J'essaie d'écrire un petit script pour changer le répertoire courant en mon répertoire de projet:

#!/bin/bash
cd /home/tree/projects/java

J'ai enregistré ce fichier en tant que projet, ajouté une autorisation d'exécution avec chmodet copié dans /usr/bin. Quand je l'appelle par:, projça ne fait rien. Qu'est-ce que je fais mal?

ashokgelal
la source
10
double site croisé: superuser.com/questions/176783/…
lesmana
À l'avenir, vous pouvez toujours essayer de le tester avec pwdsur la dernière ligne. Donc, avant la fin du script, vous pouvez vérifier si cela fonctionne ou non ..
sobi3ch
5
@lesmana comment est-ce un doublon?
aland
@aland Parce que OP n'exécute pas le script, c'est pourquoi le répertoire de travail ne change pas pour lui. cdLa commande fonctionne bien à l'intérieur des scripts, essayez par vous-même.
Alexander Gonchiy

Réponses:

635

Les scripts shell sont exécutés à l'intérieur d'un sous-shell, et chaque sous-shell a son propre concept de ce qu'est le répertoire actuel. Le cdréussit, mais dès que le sous-shell se termine, vous êtes de retour dans le shell interactif et rien n'y change.

Une façon de contourner ce problème consiste à utiliser un alias à la place:

alias proj="cd /home/tree/projects/java"
Greg Hewgill
la source
7
Les alias ne sont pas si flexibles à gérer ou à modifier. Dans le cas de nombreux cd, les scripts pourraient être meilleurs.
Thevs
16
Les fonctions sont plus flexibles que les alias, c'est donc là que vous regardez ensuite lorsque les alias ne suffisent pas.
éphémère
4
Est-il utile de noter que sur MS-DOS, le comportement des scripts était qu'un script appelé pouvait changer le répertoire (et même le lecteur) du shell de commande appelant? Et qu'Unix n'a pas ce défaut?
Jonathan Leffler
23
Jonathan: bien que ce soit vrai, ce n'est pas vraiment lié à la question. Les réponses sur SO obtiendraient deux fois plus de temps si elles devaient répertorier chacune des lacunes correspondantes dans MS-DOS!
Greg Hewgill
17
Une autre façon de le contourner est de source le fichier de script: . my-scriptou source my-script.
HelloGoodbye
505

Tu ne fais rien de mal! Vous avez modifié le répertoire, mais uniquement dans le sous-shell qui exécute le script.

Vous pouvez exécuter le script dans votre processus actuel avec la commande "dot":

. proj

Mais je préfère la suggestion de Greg d'utiliser un alias dans ce cas simple.

Adam Liss
la source
95
.est également orthographié source, choisissez celui que vous trouvez le plus mémorable.
éphémère
47
@Ephemient: Bonne source de points Cela explique pourquoi cela fonctionne Source prouve également que la paresse, et non la nécessité, est souvent la mère de la source d'inventions
Adam Liss
8
@ephemient: notez que la source est utilisée dans le shell C et Bash; il n'est pas pris en charge dans les shells POSIX ou Korn, ni dans le shell Bourne classique.
Jonathan Leffler
2
"source" est utilisé dans Z-shell
Nathan Moos
1
@AdamLiss Cela fonctionne très bien, mais il y a un inconvénient - en quelque sorte, la commande source / dot désactive la possibilité de compléter automatiquement le nom du script / commande avec la touche TAB. Il est toujours possible d'ajouter des arguments / entrées avec la touche TAB, mais malheureusement le nom de la commande doit être entré directement. Savez-vous comment faire fonctionner la touche TAB dans ce cas?
Rafal
215

Le cddans votre script fonctionnait techniquement car il changeait le répertoire du shell qui exécutait le script, mais c'était un processus distinct issu de votre shell interactif.

Un moyen compatible Posix pour résoudre ce problème consiste à définir une procédure shell plutôt qu'un script de commande appelé shell .

jhome () {
  cd /home/tree/projects/java
}

Vous pouvez simplement taper ceci ou le mettre dans l'un des différents fichiers de démarrage du shell.

DigitalRoss
la source
6
Je suis d'accord, c'est la meilleure réponse, heurtée. De plus, l'alias peut être approprié dans certaines situations, mais s'il essaie d'utiliser cd dans un script et souhaite y ajouter autre chose, un alias serait inutile.
Justin Buser
4
Cela ressemble à une solution! Et j'essaie de l'implémenter, mais il ne fonctionne pas :( voici mon script: #! / Bin / bash jhome () {echo "please" cd / home echo "work"} jhome
Gant Laborde
1
@GantMan, vous devez l'ajouter dans un "fichier de démarrage du shell", comme ~ / .bashrc
Jackie Yeh
3
Vous pouvez également utiliser un argument (ou deux ...), c'est-à-dire:jhome(){ cd /home/tree/projects/$1; }
irbanana
1
Cela devrait être la bonne façon, cela permet d'utiliser des caractères spéciaux, comme "-" par exemple.
bangkokguy
159

Le cdse fait dans le shell du script. Lorsque le script se termine, ce shell se ferme, puis vous vous retrouvez dans le répertoire où vous vous trouviez. "Source" le script, ne l'exécutez pas. Au lieu de:

./myscript.sh

faire

. ./myscript.sh

(Remarquez le point et l'espace avant le nom du script.)

Tzachi.e
la source
2
C'est cool, et probablement bon à savoir. Mais que fait ce dernier exactement (comment ça marche)? C'est probablement la meilleure solution sur ce fil.
Jonah
2
fwiw, dans la section des commentaires de la réponse d'Adam Liss, l'éphémient répond à la question de ce que le '.' est. C'est la même chose quesource
g19fanatic
1
Attention, "source" est un bashisme. ie dash (Debian par défaut pour / bin / sh) ne le supporte pas, tandis que "." fonctionne comme prévu.
allo
Très bonne réponse! également si se myscript.shtrouve dans un répertoire inclus dans $ PATH, vous pouvez le source de n'importe où sans spécifier le chemin complet.
CodeBrew
Cela a fonctionné le mieux pour moi. Cette solution est la plus simple (+1).
HuserB1989
96

Pour créer un script bash qui sera cd dans un répertoire sélectionné:

Créer le fichier script

#! / bin / sh
# fichier: / scripts / cdjava
#
cd / home / askgelal / projets / java

Créez ensuite un alias dans votre fichier de démarrage.

#! / bin / sh
# fichier /scripts/mastercode.sh
#
alias cdjava = '. / scripts / cdjava '

  • J'ai créé un fichier de démarrage où je vide tous mes alias et fonctions personnalisées.
  • Ensuite, je source ce fichier dans mon .bashrc pour le définir à chaque démarrage.

Par exemple, créez un fichier d'alias / fonctions principal: /scripts/mastercode.sh ( placez
l'alias dans ce fichier.)

Puis à la fin de votre fichier .bashrc :

source /scripts/mastercode.sh



Maintenant, c'est facile de cd dans votre répertoire java, tapez simplement cdjava et vous y êtes.

Matt Thomas
la source
2
le fichier mastercode.shn'a pas besoin de shabang ( #!/bin/sh), car il n'est pas (et ne peut pas être) exécuté dans un sous-shell. Mais en même temps, vous devez documenter la "saveur" du shell de ce fichier; par exemple, ksh ou bash (ou (t) csh / zsh, etc.), et ce n'est certainement pas le cas sh. J'ajoute habituellement un commentaire (mais pas le shebang) pour communiquer cela; par exemple, "ce fichier est censé provenir (de bash), ne pas être exécuté comme un script shell."
michael
Autre astuce (pour bash): si vous utilisez des variables dans votre script, alors que le script est en cours sourced (via le alias), ces variables vont s'infiltrer dans votre environnement shell. Pour éviter cela, faites tout le travail du script dans une fonction et appelez-la simplement à la fin du script. Dans la fonction, déclarez toutes les variables local.
Rhubbarb
dans ubuntu, créez simplement ~ / .bash_aliases (s'il n'existe pas déjà). Ensuite, ajoutez simplement vos alias, redémarrez le terminal et c'est fait.
Vlad
51

Vous pouvez utiliser .pour exécuter un script dans l'environnement shell actuel:

. script_name

ou bien, son alias plus lisible mais spécifique au shell source:

source script_name

Cela évite le sous-shell, et permet à toutes les variables ou commandes (y compris cd) d'affecter le shell actuel à la place.

Sagar
la source
38

L'idée de Jeremy Ruten d'utiliser un lien symbolique a déclenché une pensée qui n'a croisé aucune autre réponse. Utilisation:

CDPATH=:$HOME/projects

Le côlon principal est important; cela signifie que s'il y a un répertoire 'dir' dans le répertoire courant, alors ' cd dir' changera pour cela, plutôt que de sauter ailleurs. Avec la valeur définie comme indiqué, vous pouvez faire:

cd java

et, s'il n'y a pas de sous-répertoire appelé java dans le répertoire courant, alors il vous amènera directement à $ HOME / projects / java - pas d'alias, pas de scripts, pas d'execs douteux ou de commandes dot.

Mon $ HOME est / Users / jleffler; mon $ CDPATH est:

:/Users/jleffler:/Users/jleffler/mail:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work
Jonathan Leffler
la source
31

Utilisation exec bashà la fin

Un script bash opère sur son environnement actuel ou sur celui de ses enfants, mais jamais sur son environnement parent.

Cependant, cette question est souvent posée car on veut être laissé à une (nouvelle) invite bash dans un certain répertoire après l'exécution d'un script bash à partir d'un autre répertoire.

Si tel est le cas, exécutez simplement une instance bash enfant à la fin du script:

#!/usr/bin/env bash
cd /home/tree/projects/java
exec bash
Serge Stroobandt
la source
16
Cela crée un nouveau sous-shell. Lorsque vous tapez, exitvous revenez au shell où vous avez exécuté ce script.
tripleee
1
Lorsque le script a été appelé plusieurs fois, il semble qu'il crée des coquilles imbriquées et je dois taper 'exit' plusieurs fois. Cela pourrait-il être résolu d'une manière ou d'une autre?
VladSavitsky
Voir les autres réponses: utilisez un alias, utilisez "pushd / popd / dirs", ou utilisez "source"
qneill
25

J'ai fait fonctionner mon code en utilisant. <your file name>

./<your file name> dose ne fonctionne pas car il ne modifie pas votre répertoire dans le terminal, il modifie simplement le répertoire spécifique à ce script.

Voici mon programme

#!/bin/bash 
echo "Taking you to eclipse's workspace."
cd /Developer/Java/workspace

Voici mon terminal

nova:~ Kael$ 
nova:~ Kael$ . workspace.sh
Taking you to eclipe's workspace.
nova:workspace Kael$ 
kaelhop
la source
2
Quelle est la différence entre . somethinget ./something?? Cette réponse a fonctionné pour moi et je ne comprends pas pourquoi.
Dracorat
. somethingvous permet d'exécuter le script à partir de n'importe quel emplacement, ./somethingvous oblige à être dans le répertoire dans
lequel
Pouvez-vous mettre le point dans la ligne de shebang? #!. /bin/bash?
Sigfried
20

exécutez simplement:

cd /home/xxx/yyy && command_you_want
warhansen
la source
Cela a fonctionné pour moi merci @warhansen
Ashish Kumar
12

Lorsque vous lancez un script shell, il exécute une nouvelle instance de ce shell ( /bin/bash). Ainsi, votre script lance simplement un shell, change le répertoire et quitte. Autrement dit, cd(et d'autres commandes de ce type) dans un script shell n'affectent ni n'ont accès au shell à partir duquel elles ont été lancées.

Daniel Spiewak
la source
11

Dans mon cas particulier, j'avais besoin de trop de fois pour changer pour le même répertoire. Donc sur mon .bashrc (j'utilise ubuntu) j'ai ajouté le

1 -

$ nano ~. / bashrc

 function switchp
 {
    cd /home/tree/projects/$1
 }

2-

$ source ~ / .bashrc

3 -

$ switchp java

Directement, cela fera: cd / home / tree / projects / java

J'espère que cela pourra aider!

rêveur
la source
1
@WM Vous pouvez éventuellement faire "$ switchp" sans aucun paramètre pour aller directement à l'arborescence du projet
workdreamer
2
@workdreamer L'approche de fonction que vous avez décrite ci-dessus a résolu un petit problème ici en entrant dans des sous-dossiers qui ont le même dossier parent sur mon système. Je vous remercie.
WM
10

Vous pouvez faire ce qui suit:

#!/bin/bash
cd /your/project/directory
# start another shell and replacing the current
exec /bin/bash

EDIT: Cela pourrait également être «pointillé», pour empêcher la création de coques ultérieures.

Exemple:

. ./previous_script  (with or without the first line)
Thevs
la source
1
Cela devient plutôt désordonné après quelques exécutions .. Vous devrez exit(ou ctrl + d) plusieurs fois pour quitter le shell, par exemple .. Un alias est tellement plus propre (même si la commande shell génère un répertoire, et qu'il est cd à la sortie - quelque chose d'alias = "cd getnewdirectory.sh")
dbr
Notez le «exec». Il fait remplacer la vieille coquille.
Thevs
2
L'exéc remplace uniquement le sous-shell qui exécutait la commande cd, pas le shell qui a exécuté le script. Si vous aviez parsemé le script, vous auriez raison.
Jonathan Leffler,
Faites en pointillé - pas de problème. Je viens de proposer une solution. Cela ne dépend pas de la façon dont vous lancez cela.
Thevs
2
Hehe, je pense qu'il vaut mieux juste 'dot' script d'une ligne avec seulement la commande cd :) Je garderai ma réponse quand même ... Ce sera une réponse stupide correcte à une question stupide incorrecte :)
Thevs
7

Il ne change que le répertoire du script lui-même, tandis que votre répertoire actuel reste le même.

Vous voudrez peut-être utiliser un lien symbolique à la place. Il vous permet de créer un "raccourci" vers un fichier ou un répertoire, vous n'avez donc qu'à taper quelque chose comme cd my-project.

Jeremy Ruten
la source
Avoir ce lien symbolique dans chaque répertoire serait une nuisance. Il serait possible de mettre le lien symbolique dans $ HOME et ensuite de faire 'cd ~ / mon-projet'. Franchement, cependant, il est plus simple d'utiliser CDPATH.
Jonathan Leffler
7

Vous pouvez combiner les approches d'alias et de points d'Adam et Greg pour créer quelque chose de plus dynamique ...

alias project=". project"

Maintenant, l'exécution de l'alias du projet exécutera le script du projet dans le shell actuel par opposition au sous-shell.

robertmoggach
la source
C'est exactement ce dont j'avais besoin pour maintenir tout mon script et simplement l'appeler depuis bash et maintenir la session en cours - c'est la réponse PARFAITE.
Matt The Ninja
7

Vous pouvez combiner un alias et un script,

alias proj="cd \`/usr/bin/proj !*\`"

à condition que le script fasse écho au chemin de destination. Notez que ce sont des backticks entourant le nom du script. 

Par exemple, votre script pourrait être

#!/bin/bash
echo /home/askgelal/projects/java/$1

L'avantage de cette technique est que le script peut prendre n'importe quel nombre de paramètres de ligne de commande et émettre différentes destinations calculées par une logique éventuellement complexe.

JA Faucett
la source
3
Pourquoi? vous pouvez simplement utiliser: proj() { cd "/home/user/projects/java/$1"; }=> proj "foo"(ou, proj "foo bar"<= dans le cas où vous avez des espaces) ... ou même (par exemple): proj() { cd "/home/user/projects/java/$1"; shift; for d; do cd "$d"; done; }=> proj a b c=> fait un cdinto/home/user/projects/java/a/b/c
michael
5

Dans votre fichier ~ / .bash_profile. ajouter la fonction suivante

move_me() {
    cd ~/path/to/dest
}

Redémarrez le terminal et vous pouvez taper

move_me 

et vous serez déplacé vers le dossier de destination.

mihai.ciorobea
la source
3

Vous pouvez utiliser l'opérateur &&:

cd myDirectory && ls

Jack Bauer
la source
Downvote: Cela ne tente pas de répondre à la question réelle ici. Vous pouvez exécuter deux commandes à l'invite (avec &&si vous voulez que la seconde soit conditionnelle à la première, ou simplement avec ;ou une nouvelle ligne entre elles) mais le mettre dans un script vous ramènera à "pourquoi le shell parent n'utilise-t-il pas le nouveau répertoire lorsque le cda réussi? "
tripleee
3

Bien que la recherche du script que vous souhaitez exécuter soit une solution, vous devez savoir que ce script peut alors modifier directement l'environnement de votre shell actuel. De plus, il n'est plus possible de passer des arguments.

Une autre façon de faire est d'implémenter votre script en tant que fonction dans bash.

function cdbm() {
  cd whereever_you_want_to_go
  echo "Arguments to the functions were $1, $2, ..."
}

Cette technique est utilisée par autojump: http://github.com/joelthelion/autojump/wiki pour vous fournir des signets d'apprentissage dans le répertoire shell.

thomasd
la source
3

Vous pouvez créer une fonction comme ci-dessous dans votre .bash_profileet cela fonctionnera sans problème.

La fonction suivante prend un paramètre facultatif qui est un projet. Par exemple, vous pouvez simplement exécuter

cdproj

ou

cdproj project_name

Voici la définition de la fonction.

cdproj(){
    dir=/Users/yourname/projects
    if [ "$1" ]; then
      cd "${dir}/${1}"
    else
      cd "${dir}"
    fi
}

N'oubliez pas de vous procurer votre .bash_profile

Krish
la source
1
réponse bien meilleure que l'alias
Pax0r
Cette solution est brillante par sa simplicité et sa flexibilité.
Kjartan
3

Cela devrait faire ce que vous voulez. Accédez au répertoire d'intérêt (à partir du script), puis générez un nouveau shell bash.

#!/bin/bash

# saved as mov_dir.sh
cd ~/mt/v3/rt_linux-rt-tools/
bash

Si vous l'exécutez, il vous amènera au répertoire d'intérêt et lorsque vous le quitterez, il vous ramènera à l'emplacement d'origine.

root@intel-corei7-64:~# ./mov_dir.sh

root@intel-corei7-64:~/mt/v3/rt_linux-rt-tools# exit
root@intel-corei7-64:~#

Cela vous ramènera même à votre répertoire d'origine lorsque vous quitterez ( CTRL+ d)

jithu83
la source
3
Générer un nouveau bash signifie que vous perdrez votre historique et qu'il recommencera à zéro.
Tisch
2

LOOOOOng après, mais j'ai fait ce qui suit:

créer un fichier appelé case

collez ce qui suit dans le fichier:

#!/bin/sh

cd /home/"$1"

enregistrez-le puis:

chmod +x case

J'ai également créé un alias dans mon .bashrc:

alias disk='cd /home/; . case'

maintenant quand je tape:

case 12345

essentiellement je tape:

cd /home/12345

Vous pouvez taper n'importe quel dossier après «case»:

case 12

case 15

case 17

ce qui revient à taper:

cd /home/12

cd /home/15

cd /home/17

respectivement

Dans mon cas, le chemin est beaucoup plus long - ces gars-là l'ont résumé avec les ~ info plus tôt.

chris
la source
1
Le cddans l'alias est superflu et inélégant; l'alias devrait simplement '. ~ / case` à la place. Est également caseun mot clé réservé, donc un choix plutôt mauvais pour un nom.
tripleee
1

Vous n'avez besoin d'aucun script, définissez uniquement l'option correcte et créez une variable d'environnement.

shopt -s cdable_vars

dans votre ~/.bashrcpermet au cdcontenu des variables d'environnement.

Créez une telle variable d'environnement:

export myjava="/home/tree/projects/java"

et vous pouvez utiliser:

cd myjava

D'autres alternatives .

Gauthier
la source
0

Si vous utilisez du poisson comme coquille, la meilleure solution est de créer une fonction. À titre d'exemple, compte tenu de la question d'origine, vous pouvez copier les 4 lignes ci-dessous et les coller dans votre ligne de commande poisson:

function proj
   cd /home/tree/projects/java
end
funcsave proj

Cela créera la fonction et l'enregistrera pour une utilisation ultérieure. Si votre projet change, répétez simplement le processus en utilisant le nouveau chemin.

Si vous préférez, vous pouvez ajouter manuellement le fichier de fonction en procédant comme suit:

nano ~/.config/fish/functions/proj.fish

et entrez le texte:

function proj
   cd /home/tree/projects/java
end

et enfin appuyez sur ctrl + x pour quitter et y suivi de retour pour enregistrer vos modifications.

( REMARQUE: la première méthode d'utilisation de funcsave crée le fichier proj.fish pour vous ).

Lane Roathe
la source
0

J'ai un simple script bash appelé p pour gérer le changement de répertoire sur
github.com/godzilla/bash-stuff il
suffit de mettre le script dans votre répertoire bin local (/ usr / local / bin)
et de mettre

alias p='. p'

dans votre .bashrc

godzilla
la source
qu'est-ce que cela fait? il semble qu'il fonctionnerait de manière récursive, bien que je sois sûr si vous l'avez exécuté avant qu'il ne fonctionne pas;)
Ryan Taylor
0

Notez la discussion Comment définir le répertoire de travail du processus parent?

Il contient des réponses piratées, par exemple https://stackoverflow.com/a/2375174/755804 (modification du répertoire de processus parent via gdb, ne faites pas cela) et https://stackoverflow.com/a/51985735/755804 ( la commande tailcdqui injecte cd dirname dans le flux d'entrée du processus parent; eh bien, idéalement, cela devrait être une partie de bash plutôt qu'un hack)

18446744073709551615
la source
0

C'est une vieille question, mais je suis vraiment surpris de ne pas voir cette astuce ici

Au lieu d'utiliser un CD, vous pouvez utiliser

export PWD=the/path/you/want

Pas besoin de créer de sous-shell ou d'utiliser des alias.

Notez qu'il est de votre responsabilité de vous assurer que le / chemin / vous / voulez existe.

Yuri Nudelman
la source
Cela n'a pas fonctionné, malheureusement.
ttfreeman
0

Comme expliqué dans les autres réponses, vous avez modifié le répertoire, mais uniquement dans le sous-shell qui exécute le script . cela n'a pas d'impact sur le shell parent.

Une solution consiste à utiliser les fonctions bash au lieu d'un script bash ( sh); en plaçant votre code de script bash dans une fonction. Cela rend la fonction disponible en tant que commande et ensuite, cela sera exécuté sans processus enfant et donc toute cdcommande aura un impact sur le shell de l'appelant.

Fonctions bash:

Une caractéristique du profil bash est de stocker des fonctions personnalisées qui peuvent être exécutées dans le terminal ou dans des scripts bash de la même manière que vous exécutez des applications / commandes, cela pourrait également être utilisé comme raccourci pour de longues commandes.

Pour que votre système fonctionne efficacement, vous devrez copier votre fonction à la fin de plusieurs fichiers

/home/user/.bashrc
/home/user/.bash_profile
/root/.bashrc
/root/.bash_profile

Vous pouvez sudo kwrite /home/user/.bashrc /home/user/.bash_profile /root/.bashrc /root/.bash_profilemodifier / créer ces fichiers rapidement

Comment :

Copiez le code de votre script bash dans une nouvelle fonction à la fin du fichier de profil de votre bash et redémarrez votre terminal, vous pouvez ensuite exécuter cddou quelle que soit la fonction que vous avez écrite.

Exemple de script

Faire un raccourci cd ..aveccdd

cdd() {
  cd ..
}

raccourci ls

ll() {
  ls -l -h
}

raccourci ls

lll() {
  ls -l -h -a
}
intika
la source
-2

Vous pouvez exécuter certaines lignes dans le même sous-shell si vous terminez les lignes avec une barre oblique inverse.

cd somedir; \
pwd
Max
la source