Why can't I change directories using "cd" in a script?
Asked Answered
I

33

828

I'm trying to write a small script to change the current directory to my project directory:

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

I saved this file as proj, added execute permission with chmod, and copied it to /usr/bin. When I call it by: proj, it does nothing. What am I doing wrong?

Infernal answered 1/11, 2008 at 2:7 Comment(8)
cross site duplicate: superuser.com/questions/176783/…Bennett
In future you can always try test it with pwd on last line. So before script finish then you can check is it working or not..Grommet
@lesmana how is that a duplicate?Granulate
@Granulate Because OP does not in fact run the script, that's why the working dir doesn't change for him. cd command works well inside of scripts, try for yourself.Bedridden
Why is cd not a program?, where is cd located?Plumy
xdotool type --clearmodifiers 'cd ~/projects/java' && xdotool key KP_EnterFoliolate
Fundamentally this is a duplicate of #1464753Messroom
cross-site duplicates: why cd function in script doesn't work, Why can't I use cd in a bash script?, Why doesn't "cd" work in a shell script?Plumy
A
697

Shell scripts are run inside a subshell, and each subshell has its own concept of what the current directory is. The cd succeeds, but as soon as the subshell exits, you're back in the interactive shell and nothing ever changed there.

One way to get around this is to use an alias instead:

alias proj="cd /home/tree/projects/java"
Afroamerican answered 1/11, 2008 at 2:9 Comment(9)
Aliases aren't so flexible to manage or change. In case of many 'cd's, scripts could be better.Ferdinandferdinanda
Functions are more flexible than aliases, so that's where you'd look next when aliases aren't enough.Incommensurate
I think this discussion gets pointless. "There are zillions ways to make things in Perl" (tm)Ferdinandferdinanda
+1 for the excellent explanation, coupled with a helpful alternative!Weepy
Is it worth noting that on MS-DOS, the behaviour of scripts was that a called script could change the directory (and even drive) of the calling command shell? And that Unix does not have this defect?Jackdaw
Jonathan: while that's true, it's not really related to the question. Answers on SO would get twice as long if they had to each list the corresponding deficiencies in MS-DOS!Afroamerican
s/defect/side effect/; s/worth noting/not &/Disrelish
@FedericoA.Ramponi or in ~/.bash_aliasesLampblack
Another way to get around it is to source the script file: . my-script or source my-script.Exasperate
W
560

You're doing nothing wrong! You've changed the directory, but only within the subshell that runs the script.

You can run the script in your current process with the "dot" command:

. proj

But I'd prefer Greg's suggestion to use an alias in this simple case.

Weepy answered 1/11, 2008 at 2:9 Comment(9)
. is also spelled source, choose whichever you find more memorable.Incommensurate
@Ephemient: Good pointsource That explains why it workssource Also proves that laziness-not necessity-is often the mother of inventionsourceWeepy
@ephemient: note that source is used in C shell and Bash; it is not supported in POSIX or Korn shells, nor in classic Bourne shell.Jackdaw
"source" is used in Z-shellOverissue
@AdamLiss It works great, but there is one drawback - somehow source/dot command disables possibility to auto-complete name of the script/command with TAB key. It is still possible to add arguments/input with TAB key, but unfortunately name of the command needs to be input directly. Do you know how to make TAB key work in this case?Waterresistant
@Waterresistant I tested this on my system (from a PuTTY terminal). I was able to tab complete the name of the command with no problem for both dot and 'source'. Might be your environment?Vassell
@Vassell You are right. I just tested this on Ubuntu 14.04 and tab completion works great. It must have been an issue with Ubuntu 13.10 back in January 2014 or maybe my configuration. Thanks for info.Waterresistant
Another option is to combine both approaches, if you want a complex behaviour write it in an executalbe called myscript, and in .bashrc write alias myscript=". myscript"Ibnsaud
@J27avier: Wow, 15 years later, and still relevant! Your suggestion is super useful, and I've been using an embellished version of it for years. I keep "simple" aliases in my .bash_aliases file, which ends with the line [[ ! -f ~/.bash_functions ]] || . ~/.bash_functions. I keep more complex items in that second file, e.g., ff() { dir="$1"; name="$2"; shift 2; find "$dir" -name "$name" -type f | xargs grep "$@"; }. And, of course, alias al='vi ~/.bash_aliases && . ~/.bash_aliases' lets me edit the aliases and have them take immediate effect.Weepy
G
234

The cd in your script technically worked as it changed the directory of the shell that ran the script, but that was a separate process forked from your interactive shell.

A Posix-compatible way to solve this problem is to define a shell procedure rather than a shell-invoked command script.

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

You can just type this in or put it in one of the various shell startup files.

Garzon answered 11/8, 2011 at 4:20 Comment(7)
I agree, this is the best answer, bumped. Also, alias may be appropriate in some situations but if he's trying to use cd in a script and wanted to add anything else to it an alias would be useless.Macias
This looks like a solution! And I'm trying to implement it, but it's not performing :( here's my script: #!/bin/bash jhome() { echo "please" cd /home echo "work" } jhomeBiagi
@GantMan, you should add this in a"shell startup files", like ~/.bashrcPelagian
If it's in a file you need to use . filename or source filename so that the current shell sees it, rather than the subshell that ran the command. Or take Jackie's advice and put it in something like ~/.bashrc.Garzon
You can also use an argument (or two...), ie: jhome(){ cd /home/tree/projects/$1; }Slickenside
This should be the right way, this makes it possible to use special characters, like "-" for example.Trajan
Perfect, just solved my problem flawlessly. This should be marked has the right answer.Wintry
J
180

The cd is done within the script's shell. When the script ends, that shell exits, and then you are left in the directory you were. "Source" the script, don't run it. Instead of:

./myscript.sh

do

. ./myscript.sh

(Notice the dot and space before the script name.)

Jakejakes answered 10/2, 2010 at 12:7 Comment(5)
This is cool, and probably good to know. What does the latter do exactly though (how does it work)? This is probably the best solution on this thread.Bourassa
fwiw, in the comments section of Adam Liss' answer, ephemient answers the question of what the '.' is. It is the same thing as sourceLeptospirosis
Be careful, "source" is a bashism. i.e. dash (Debian default for /bin/sh) does not support it, while "." works as expected.Tenderize
Great answer! also if myscript.sh is in a directory included in $PATH, you can source it from anywhere without specifying the full path.Caphaitien
This worked for me the best. This solution is the simplest (+1).Necrolatry
T
103

To make a bash script that will cd to a select directory :

Create the script file

#!/bin/sh
# file : /scripts/cdjava
#
cd /home/askgelal/projects/java

Then create an alias in your startup file.

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

  • I created a startup file where I dump all my aliases and custom functions.
  • Then I source this file into my .bashrc to have it set on each boot.

For example, create a master aliases/functions file: /scripts/mastercode.sh
(Put the alias in this file.)

Then at the end of your .bashrc file:

source /scripts/mastercode.sh



Now its easy to cd to your java directory, just type cdjava and you are there.

Tufted answered 6/5, 2010 at 3:41 Comment(3)
the file mastercode.sh doesn't need the shabang (#!/bin/sh), since it is not (and can not be) executed in a subshell. But at the same time, you do need to document the shell "flavor" of this file; e.g., ksh or bash (or (t)csh/zsh,etc), and it's almost certainly not actually sh. I usually add a comment (but not the shebang) to communicate this; e.g., "this file is meant to be sourced (from bash), not run as a shell script."Disrelish
Another tip (for bash): if you use variables in your script, then as the script is being sourced (via the alias), those variables will leak into your shell environment. To avoid that, do all the work of the script in a function, and just call it at the end of the script. Within the function, declare any variables local.Theocentric
in ubuntu just create ~/.bash_aliases (if it doesn't exist already). Then just add your aliases there, restart the terminal and your done.Mechanician
G
58

You can use . to execute a script in the current shell environment:

. script_name

or alternatively, its more readable but shell specific alias source:

source script_name

This avoids the subshell, and allows any variables or builtins (including cd) to affect the current shell instead.

Guck answered 10/2, 2014 at 11:59 Comment(0)
B
41

Use exec bash at the end

A bash script operates on its current environment or on that of its children, but never on its parent environment.

However, this question often gets asked because one wants to be left at a (new) bash prompt in a certain directory after execution of a bash script from within another directory.

If this is the case, simply execute a child bash instance at the end of the script:

#!/usr/bin/env bash
cd /home/tree/projects/java
echo -e '\nHit [Ctrl]+[D] to exit this child shell.'
exec bash

To return to the previous, parental bash instance, use Ctrl+D.

Update

At least with newer versions of bash, the exec on the last line is no longer required. Furthermore, the script could be made to work with whatever preferred shell by using the $SHELL environment variable. This then gives:

#!/usr/bin/env bash
cd desired/directory
echo -e '\nHit [Ctrl]+[D] to exit this child shell.'
$SHELL
Breadnut answered 21/4, 2016 at 11:34 Comment(7)
This creates a new subshell. When you type exit you will return to the shell where you ran this script.Messroom
When script was called several times seems it creates nested shells and I need to type 'exit' a lot of times. Could this be solved somehow?Bartz
See the other answers: use an alias, use "pushd/popd/dirs", or use "source"Diversity
@Acumenus You are absolutely right. The exec was required with older versions of bash and possibly other shells. I updated the answer accordingly.Breadnut
After running exec bash can I remain in the script and continue to run another commands?Subaudition
@Subaudition Yes, certainly. More commands can be executed in the new child bash instance. To return to the previous, parental bash instance, use [Ctrl]+[D].Breadnut
This would work for me if it wasn't for the fact I rely on the shell history to autocomplete commands! Thanks for posting though.Seaborne
J
40

Jeremy Ruten's idea of using a symlink triggered a thought that hasn't crossed any other answer. Use:

CDPATH=:$HOME/projects

The leading colon is important; it means that if there is a directory 'dir' in the current directory, then 'cd dir' will change to that, rather than hopping off somewhere else. With the value set as shown, you can do:

cd java

and, if there is no sub-directory called java in the current directory, then it will take you directly to $HOME/projects/java - no aliases, no scripts, no dubious execs or dot commands.

My $HOME is /Users/jleffler; my $CDPATH is:

:/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
Jackdaw answered 1/11, 2008 at 4:21 Comment(0)
D
28

I got my code to work by using. <your file name>

./<your file name> dose not work because it doesn't change your directory in the terminal it just changes the directory specific to that script.

Here is my program

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

Here is my terminal

nova:~ Kael$ 
nova:~ Kael$ . workspace.sh
Taking you to eclipe's workspace.
nova:workspace Kael$ 
Demulsify answered 28/10, 2012 at 19:42 Comment(4)
What's the difference between . something and ./something?? This answer worked for me and I don't understand why.Fencible
. something allows you to run the script from any location, ./something requires you to be in the directory the file is stored in.Litchfield
@Fencible #13786999Messroom
Can you put the dot in the shebang line? #!. /bin/bash?Damick
C
21

simply run:

cd /home/xxx/yyy && command_you_want
Cough answered 10/3, 2017 at 14:33 Comment(0)
F
13

When you fire a shell script, it runs a new instance of that shell (/bin/bash). Thus, your script just fires up a shell, changes the directory and exits. Put another way, cd (and other such commands) within a shell script do not affect nor have access to the shell from which they were launched.

Farias answered 1/11, 2008 at 2:9 Comment(0)
F
12

You can do following:

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

EDIT: This could be 'dotted' as well, to prevent creation of subsequent shells.

Example:

. ./previous_script  (with or without the first line)
Ferdinandferdinanda answered 1/11, 2008 at 2:46 Comment(9)
That gets rather messy after a few runs.. You will have to exit (or ctrl+d) several times to exit the shell, for example.. An alias is so much cleaner (even if the shell command outputs a directory, and it cd's to the output - alias something="cd getnewdirectory.sh")Dialyze
Note the 'exec'. It makes replace of old shell.Ferdinandferdinanda
The exec only replaces the sub-shell that was running the cd command, not the shell that ran the script. Had you dotted the script, then you'd be correct.Jackdaw
Make it 'dotted' - no problem. I just proposed solution. It doesn't depend on how you launch this.Ferdinandferdinanda
Hehe, I think it's better just to 'dot' one-line script with only cd command :) I'll keep my answer anyway... That will be correct stupid answer to incorrect stupid question :)Ferdinandferdinanda
#874952 is what i would propose for interactive usage to launch a dev env.Debbee
exec /bin/bash did what I needed. Thanks!Pennate
This helped me to address a loong pending problem for me. Thanks a lot.Buttery
exec zsh for zshOvariectomy
L
12

On my particular case i needed too many times to change for the same directory. So on my .bashrc (I use ubuntu) i've added the

1 -

$ nano ~./bashrc

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

2-

$ source ~/.bashrc

3 -

$ switchp java

Directly it will do: cd /home/tree/projects/java

Hope that helps!

Lacrimatory answered 14/9, 2012 at 10:53 Comment(2)
@W.M. You can eventually do "$ switchp" without any parameter to go directly to the project treeLacrimatory
@Lacrimatory The function approach you described above solved a little trouble here in going into sub-folders which have same parent folder at my system. Thank you.Decompensation
O
8

You can combine Adam & Greg's alias and dot approaches to make something that can be more dynamic—

alias project=". project"

Now running the project alias will execute the project script in the current shell as opposed to the subshell.

Outdo answered 9/2, 2012 at 7:33 Comment(1)
This is exactly what I needed to maintain all my script and just call it from bash and maintain current session - this is the PERFECT answer.Drawer
V
7

It only changes the directory for the script itself, while your current directory stays the same.

You might want to use a symbolic link instead. It allows you to make a "shortcut" to a file or directory, so you'd only have to type something like cd my-project.

Valentino answered 1/11, 2008 at 2:10 Comment(1)
Having that symlink in every directory would be a nuisance. It would be possible to put the symlink in $HOME and then do 'cd ~/my-project'. Frankly, though, it is simpler to use CDPATH.Jackdaw
F
7

You can combine an alias and a script,

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

provided that the script echos the destination path. Note that those are backticks surrounding the script name. 

For example, your script could be

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

The advantage with this technique is that the script could take any number of command line parameters and emit different destinations calculated by possibly complex logic.

Fieldpiece answered 3/11, 2008 at 17:22 Comment(1)
why? you could just use: proj() { cd "/home/user/projects/java/$1"; } => proj "foo" (or, proj "foo bar" <= in case you have spaces)... or even (for example): proj() { cd "/home/user/projects/java/$1"; shift; for d; do cd "$d"; done; } => proj a b c => does a cd into /home/user/projects/java/a/b/cDisrelish
E
6

to navigate directories quicky, there's $CDPATH, cdargs, and ways to generate aliases automatically

http://jackndempsey.blogspot.com/2008/07/cdargs.html

http://muness.blogspot.com/2008/06/lazy-bash-cd-aliaes.html

https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-5827311.html

Ervinervine answered 2/11, 2008 at 12:50 Comment(0)
A
5

In your ~/.bash_profile file. add the next function

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

Restart terminal and you can type

move_me 

and you will be moved to the destination folder.

Adjustment answered 19/8, 2013 at 4:53 Comment(0)
R
3

You can use the operator && :

cd myDirectory && ls

Roswald answered 24/3, 2013 at 16:56 Comment(1)
Downvote: This does not attempt to answer the actual question here. You can run two commands at the prompt (with && if you want the second to be conditional on the first, or just with ; or newline between them) but putting this in a script will take you back to "why doesn't the parent shell use the new directory when the cd was actually successful?"Messroom
H
3

While sourcing the script you want to run is one solution, you should be aware that this script then can directly modify the environment of your current shell. Also it is not possible to pass arguments anymore.

Another way to do, is to implement your script as a function in bash.

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

This technique is used by autojump: http://github.com/joelthelion/autojump/wiki to provide you with learning shell directory bookmarks.

Hortense answered 1/2, 2014 at 12:40 Comment(0)
T
3

You can create a function like below in your .bash_profile and it will work smoothly.

The following function takes an optional parameter which is a project. For example, you can just run

cdproj

or

cdproj project_name

Here is the function definition.

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

Dont forget to source your .bash_profile

Tiflis answered 4/8, 2016 at 15:5 Comment(0)
T
3

This should do what you want. Change to the directory of interest (from within the script), and then spawn a new bash shell.

#!/bin/bash

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

If you run this, it will take you to the directory of interest and when you exit it it will bring you back to the original place.

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

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

This will even take you to back to your original directory when you exit (CTRL+d)

Toenail answered 2/5, 2018 at 23:54 Comment(1)
Spawning a new bash will mean you lose your history and it will start fresh.Lauds
M
2

I did the following:

create a file called case

paste the following in the file:

#!/bin/sh

cd /home/"$1"

save it and then:

chmod +x case

I also created an alias in my .bashrc:

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

now when I type:

case 12345

essentially I am typing:

cd /home/12345

You can type any folder after 'case':

case 12

case 15

case 17

which is like typing:

cd /home/12

cd /home/15

cd /home/17

respectively

In my case the path is much longer - these guys summed it up with the ~ info earlier.

Maggie answered 11/4, 2011 at 21:42 Comment(1)
The cd in the alias is superfluous and inelegant; the alias should simply ’. ~/case` instead. Also case is a reserved keyword, so a rather poor choice for a name.Messroom
B
1

As explained on the other answers, you have changed the directory, but only within the sub-shell that runs the script. this does not impact the parent shell.

One solution is to use bash functions instead of a bash script (sh); by placing your bash script code into a function. That makes the function available as a command and then, this will be executed without a child process and thus any cd command will impact the caller shell.

Bash functions :

One feature of the bash profile is to store custom functions that can be run in the terminal or in bash scripts the same way you run application/commands this also could be used as a shortcut for long commands.

To make your function efficient system widely you will need to copy your function at the end of several files

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

You can sudo kwrite /home/user/.bashrc /home/user/.bash_profile /root/.bashrc /root/.bash_profile to edit/create those files quickly

Howto :

Copy your bash script code inside a new function at the end of your bash's profile file and restart your terminal, you can then run cdd or whatever the function you wrote.

Script Example

Making shortcut to cd .. with cdd

cdd() {
  cd ..
}

ls shortcut

ll() {
  ls -l -h
}

ls shortcut

lll() {
  ls -l -h -a
}
Byars answered 10/4, 2018 at 7:50 Comment(2)
This really works. I am surprised it has not been upvoted as much as it would deserve.Germaine
@EerikSvenPuudist :) it was a late answer on a popular post I guess that reader don't reach it, thanks for the upvoteByars
A
0

If you are using fish as your shell, the best solution is to create a function. As an example, given the original question, you could copy the 4 lines below and paste them into your fish command line:

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

This will create the function and save it for use later. If your project changes, just repeat the process using the new path.

If you prefer, you can manually add the function file by doing the following:

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

and enter the text:

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

and finally press ctrl+x to exit and y followed by return to save your changes.

(NOTE: the first method of using funcsave creates the proj.fish file for you).

Aalst answered 12/12, 2012 at 16:46 Comment(0)
B
0

You need no script, only set the correct option and create an environment variable.

shopt -s cdable_vars

in your ~/.bashrc allows to cd to the content of environment variables.

Create such an environment variable:

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

and you can use:

cd myjava

Other alternatives.

Brendon answered 9/9, 2016 at 7:27 Comment(0)
D
0

Note the discussion How do I set the working directory of the parent process?

It contains some hackish answers, e.g. https://mcmap.net/q/55060/-how-do-i-set-the-working-directory-of-the-parent-process (changing the parent process directory via gdb, don't do this) and https://mcmap.net/q/55060/-how-do-i-set-the-working-directory-of-the-parent-process (the command tailcd that injects cd dirname to the input stream of the parent process; well, ideally it should be a part of bash rather than a hack)

Didymium answered 23/8, 2018 at 12:46 Comment(0)
D
0

It is an old question, but I am really surprised I don't see this trick here

Instead of using cd you can use

export PWD=the/path/you/want

No need to create subshells or use aliases.

Note that it is your responsibility to make sure the/path/you/want exists.

Desman answered 27/2, 2020 at 6:33 Comment(0)
W
0

I have to work in tcsh, and I know this is not an elegant solution, but for example, if I had to change folders to a path where one word is different, the whole thing can be done in the alias

a alias_name 'set a = `pwd`; set b = `echo $a | replace "Trees" "Tests"` ; cd $b'

If the path is always fixed, the just

a alias_name2 'cd path/you/always/need'

should work In the line above, the new folder path is set

Wassail answered 16/9, 2020 at 19:41 Comment(0)
C
0

This combines the answer by Serge with an unrelated answer by David. It changes the directory, and then instead of forcing a bash shell, it launches the user's default shell. It however requires both getent and /etc/passwd to detect the default shell.

#!/usr/bin/env bash
cd desired/directory
USER_SHELL=$(getent passwd <USER> | cut -d : -f 7)
$USER_SHELL

Of course this still has the same deficiency of creating a nested shell.

Compound answered 25/10, 2020 at 23:49 Comment(0)
C
0

instead of excute a script file and cd to the certain folder,

we can make it by:

  • source or . certain shell script
  • alias the cd command
  • define a function ant cd to folder

examples:

. script
# or
source script
alias ghqc='cd $(ghq root)/$(ghq list | fzf)'
ghqc() {
  cd $(ghq root)/$1
}
Calzada answered 9/4, 2022 at 23:58 Comment(1)
Is this not already covered by existing answers?Florettaflorette
V
-1

I have a simple bash script called p to manage directory changing on
github.com/godzilla/bash-stuff
just put the script in your local bin directory (/usr/local/bin)
and put

alias p='. p'

in your .bashrc

Vivacious answered 2/10, 2013 at 10:43 Comment(1)
what does this do? it looks like it would recursively run itself, although I'm sure if you've run it before it doesn't ;)Rattlebrain
W
-3

You can execute some lines in the same subshell if you end lines with backslash.

cd somedir; \
pwd
Wayland answered 6/4, 2013 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.