141

I want to run a script to simply change the current working directory:

#!/bin/bash
cd web/www/project

But, after I run it, the current pwd remains unchanged! How can I do that?

Sachin Divekar
  • 5,772
  • 1
  • 23
  • 20
Sony Santos
  • 1,513
  • 2
  • 10
  • 7

11 Answers11

171

It is an expected behavior. The script is run in a subshell, and cannot change the parent shell working directory. Its effects are lost when it finishes.

To change the current shell's directory permanently you should use the source command, also aliased simply as ., which runs a script in the current shell environment instead of a sub shell.

The following commands are identical:

. script

or

source script
user229044
  • 103
  • 3
enzotib
  • 50,671
  • 14
  • 120
  • 105
  • 12
    @Sony: Note that you should use `return` to escape from a script sourced in this way, not `exit` - they are like shell functions, and `exit` will exit the shell that sourced the script. – Charles Stewart Dec 19 '11 at 08:19
  • @CharlesStewart In fact, I'm not familiar with sourced scripts. Thank you! – Sony Santos Dec 19 '11 at 12:56
  • 8
    is `source ./script` the same? – amyassin Dec 19 '11 at 13:04
  • 3
    @amyassin: yes, it is – enzotib Dec 19 '11 at 13:05
  • @amyassin, . is the bourne shell syntax, source is the C shell syntax, some shells have imported both. – AProgrammer Dec 19 '11 at 16:01
  • 5
    1. `.` and `source` are equal in bash. 2. we don't need to use `./` before filename if it's in the same directory. It is ok to run only this: `. script` – sobi3ch Jun 16 '16 at 15:02
  • I use this all the time, except that I copy the script into my /usr/sbin directory which is in my $PATH for less typing. I also always use fully-qualified directory names in the scripts so they can be run from anywhere. – SDsolar Jul 24 '17 at 15:15
  • What's the relation to "./script.sh" ? I assumed it's the same as "source script.sh", but results are different – gebbissimo Jan 16 '21 at 19:28
  • @gebbissimo . in a path just means "current working directory", similar to .. which means "parent directory". It's what you always get when you run `ls -a` for example. The similarity to the . command is just a bad coincidence I think. `./` is required to execute programs that are not in PATH. If it's just an argument, like to the . command, then it's (mostly) redundant (programs can see the difference, try e.g. `find subdir` vs `find ./subdir` - you get equivalent but differently spelled output in this example). – hawk Jul 18 '21 at 11:37
70

For small tasks such as this, instead of creating script, create an alias like this,

$ alias cdproj='cd /dir/web/www/proj'

You should add this to your .bashrc file, if you want it set for every interactive shell.

Now you can run this as $ cdproj.

Sachin Divekar
  • 5,772
  • 1
  • 23
  • 20
  • 1
    You can also have the script echo the commands to be executed, and then use `eval \`./script\` ` or `eval $(./script)` to execute those commands. This is a common approach for commands that need to update the invoking shell's environment. – Keith Thompson Dec 20 '11 at 10:41
  • 2
    Just be very careful about what you output if you are going to go the `eval` approach. – jw013 Sep 14 '12 at 20:08
  • As a reminder, you can execute multiple commands in a single alias (effectively making it similar to a script). You can delimit them by semi-colons or such. – Brōtsyorfuzthrāx Oct 25 '22 at 23:49
38

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 the bash prompt in a certain directory after the execution of a bash script from another directory.

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

#!/usr/bin/env bash
cd desired/directory
exec bash

This creates a new subshell. Type Ctrl+D or exit to return to the first shell where the script was initially started.

UPDATE: Use $SHELL at the end

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

#!/usr/bin/env bash
cd desired/directory
$SHELL
Serge Stroobandt
  • 2,314
  • 3
  • 32
  • 36
  • 3
    Better to just source the script, as in accepted answer: using `exec` is typically considered the last resort of a scoundrel.. :) – neuronet Aug 16 '16 at 00:18
  • 1
    this trick doesn't work in debian 9 stretch. – vdegenne May 05 '18 at 00:41
  • 7
    This is the wrong way to go about this! – Dennis Williamson Mar 15 '19 at 22:04
  • 10
    Since nobody has detailed the problems with this (I’m looking at you, @Dennis): (1) Each time you run this, it creates a new, persistent bash process. Do it ten or twenty times in a session, and you’ll have 11 to 21 bash processes piled up. This may affect performance, and, if you try to terminate the session cleanly by typing `exit` (or Ctrl+D), you’ll have to do that 11 to 21 times. (2) Another drawback of using an executable script is that, if you set any shell options (e.g., `dotglob` or `globstar`) in your interactive shell session, you will lose them, because you’re starting a new shell. – G-Man Says 'Reinstate Monica' Apr 10 '19 at 16:39
  • 1
    Very nice solution! I've rewritten my alias in bash_profile so now it is a script stored in a separate file. I use the script to go to a newly created temporary folder. And now it is even easier to have a temporary bash session. SRP in action! Thanks! – artyom.razinov Oct 18 '19 at 13:46
  • @Acumenus You are absolutely right. The `exec` was required with older versions of `bash` and possibly other shells. I updated the answer accordingly. – Serge Stroobandt Oct 25 '20 at 10:49
  • 1
    Great solution. I can now write a script: `cdf` that given a file path it takes me to its directory: `cd "$(dirname "$1")"; $SHELL` – Marinos An Jan 27 '21 at 13:41
  • 1
    Because you started a subshell, all unexported variables and functions are lost. At the end you are forced to exit from all these subshells. – Dávid Horváth Apr 27 '21 at 12:46
  • @G-ManSays'ReinstateMonica' if you use `exec`, the current process is replaced, so nothing piles up. And I personally never change shell options interactively in normal use. – xeruf Aug 11 '21 at 10:17
  • @Xerus: Did you read the answer carefully and try doing what it suggests? Certainly there is some truth to what you say: if you type `exec bash` (or anything similar) into an *interactive* shell, it will replace the current shell process with a new copy of `bash`. But surely you know that, whenever you run a utility program (like `cp` or `ls`) *or a script,* your interactive shell forks a new process to run that program. (Of course this does not apply to built-in commands like `cd` and `pwd`.)  … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 21 '21 at 00:13
  • 1
    (Cont’d) … In the case of a script, this is a second shell process — a non-interactive, script-running shell process — in addition to the interactive shell process. When the script reaches the end or executes an ``exit`` command, the script-running shell process terminates. But when the script does ``exec bash``, the script-running shell process is replaced with a new, interactive shell process, in addition to the one that launched the script. So, yes, bash processes pile up. – G-Man Says 'Reinstate Monica' Aug 21 '21 at 00:13
  • Consider testing this approach yourself. Run the script a few times and check with something like `pstree` what type of processes this creates. You will notice that each time you run the script a new shell process is created, without replacing the invoking interactive shell. The `exec` would replace the non-interactive shell running the script, but not the interactive shell _invoking_ the script. As have been pointed out, this also resets several aspects of the shell, and unless special care is taken, shell's command line history is not carried forward into the new shell session either. – Kusalananda Aug 21 '21 at 06:52
  • $SHELL solved for zsh – Sham Fiorin Jan 03 '23 at 23:15
20

Depends on what you're going to do, another solution can be creating a function instead of a script.

Example:

Create a function in a file, let's say /home/aidin/my-cd-script:

function my-cd() {
  cd /to/my/path
}

Then include it in your bashrc or zshrc file:

# Somewhere in rc file
source /home/aidin/my-cd-script

Now you can use it like a command:

$ my-cd
muru
  • 69,900
  • 13
  • 192
  • 292
Aidin
  • 384
  • 2
  • 4
12

While there are answers that do the exact action that you want, a more standard method for such purpose is to create symbolic link:

ln -s ~/web/www/project proj   #use full path to dir!

Then you could cd to the directory using the name proj:

cd proj

This method is more flexible because you could access files using the short name without cd:

ls proj/   #note the endslash!
vim proj/file.x
Kevin
  • 40,087
  • 16
  • 88
  • 112
corvinus
  • 284
  • 1
  • 3
4

If you change between directories far away in the filesystem. I will recommend autojump.

stnly
  • 366
  • 2
  • 4
2

Because I functionalized a lot my cd, i did this :
added this line in ~/.bashrc

alias cd='. my_cd'

and my_cd is a script in my $PATH that does the actual cd.
To prevent recusive calls an actual cd in the script is written \cd this means "uses legacy cd not the alias".

By functionalized i mean

  1. jump to the home dir of an existing project just cd to its code.
  2. just cd in a project subdir brings me to the project home dir, not my home.
  3. cd to a project inexistent (project name have nomenclature): suggest to create the environement of the project.
  4. cd to a project that hapen to be archived : ask to revive it or just move to archive.

otherwise works like cd.

Archemar
  • 31,183
  • 18
  • 69
  • 104
1

For me the most convenient and flexible approach was a mixture of an alias and a script:

create script with arbitrary logic

Here I create a script that changes to a directory and activates the appropriate python environment. The scripts location is exmplary in /path/to/workon_myproj.sh.

#!/usr/bin/env bash

cd $HOME/workspace/myproj
source .venv/bin/activate

create alias that sources script

alias workon_myproj='source /path/to/workon_myproj.sh'

Add the alias definition into your appropriate shell start file e.g. .profile, .bashrc or .zshrc.

outcome

You can now simply execute workon_myproj in a shell which will source the content of your script in the desired directory.

extensibility

You could event improve your script to take an argument so that it works with multiple projects in a specific working directory, or combine it with a git pull to get the latest changes immediately and so on... everything boiler plate stuff you do when continuing to work on a specific project.

Jan
  • 156
  • 3
-1

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.

Asclepius
  • 376
  • 3
  • 9
-2

Why not use "exec" it seams to do exactly what I wish.

#!/bin/bash

cd someplace
exec bash

~/someplace
roaima
  • 107,089
  • 14
  • 139
  • 261
paul
  • 1
  • 6
    Beware of things that **seem** to be what you want.  (A giant wooden horse!  Just what I wanted!)  Each time you run this, it creates a new, persistent bash process.  Do it ten or twenty times in a session, and you’ll have 11 to 21 bash processes stacked up.  This may affect performance, and, if you try to terminate the session cleanly by typing ``exit`` (or Ctrl+D), you’ll have to do that 11 to 21 times. – G-Man Says 'Reinstate Monica' Apr 08 '19 at 04:23
  • I could definitely see where that would be a problem. For me though I'm using it one time, doing the work I need and then exiting.If that's the only drawback I can live with that. On the other hand if there is a better solution, I'm willing to look at it. – paul Apr 10 '19 at 14:48
  • [Aidin’s answer to this question](https://unix.stackexchange.com/q/27139/80216#450752), using a shell function, and [Sachin Divekar’s answer](https://unix.stackexchange.com/q/27139/80216#27156), using an alias, are (IMO) better solutions than using a script.  P.S. Another drawback of using a script is that, if you set any shell options (e.g., `dotglob` or `globstar`), you will lose them, because you’re starting a new shell. … (Cont’d) – G-Man Says 'Reinstate Monica' Apr 10 '19 at 16:05
  • (Cont’d) …  P.P.S.  I just noticed that you are, basically, reiterating [Serge Stroobandt’s answer](https://unix.stackexchange.com/q/27139/80216#278080) and saying ‘‘Why not do this?’’  At [SE] we expect answers to provide new ideas and/or information, and not just discuss other answers. – G-Man Says 'Reinstate Monica' Apr 10 '19 at 16:05
-2

You can do that using a function or using && The examples bellow installs Zabbix and creates a file with a line inside it.

Ex:

#!/bin/bash

# Create Function:
installZabbix(){
    cd /usr/src/zabbix-4.2.4;
    ./configure --enable-agent;
    make install;
    cd /usr/src/;
    >file;
    echo "Hi, this is a file." >>file;
}

# Call the function:
installZabbix

or:

#!/bin/bash
cd /usr/src/zabbix-4.2.4 && ./configure --enable-agent && make install && cd /usr/src && >file && echo "Hi, this is a file." >>file
Raul Chiarella
  • 172
  • 1
  • 1
  • 11