245

Is it possible to change current directory from a script?

I want to create a utility for directory navigation in Bash. I have created a test script that looks like the following:

#!/bin/bash
cd /home/artemb

When I execute the script from the Bash shell the current directory doesn't change. Is it possible at all to change the current shell directory from a script?

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
artemb
  • 9,251
  • 9
  • 48
  • 68
  • 2
    Just an enhancement suggestion: if you use `pushd` (possibly redirected to `>/dev/null` to suppress its output) instead of `cd`, you can later return to the previous directory with `popd`. – mklement0 Jun 12 '12 at 19:32
  • possible duplicate of [Why doesn't "cd" work in a bash shell script?](http://stackoverflow.com/questions/255414/why-doesnt-cd-work-in-a-bash-shell-script) – Roland Nov 25 '14 at 07:43

17 Answers17

271

When you start your script, a new process is created that only inherits your environment. When it ends, it ends. Your current environment stays as it is.

Instead, you can start your script like this:

. myscript.sh

The . will evaluate the script in the current environment, so it might be altered

Pang
  • 9,564
  • 146
  • 81
  • 122
Norbert Hartl
  • 10,481
  • 5
  • 36
  • 46
  • 7
    +1 because you're right. Though I sincerely doubt he'll want to source a directory changing script each time he needs it. Moreover, `.sh` extensions are totally eww. Don't use them. – lhunath May 17 '09 at 12:38
  • 4
    Yes, usually it is better not to use it that way. Most of the time you are glad that your current environment does not suffer. But then I have scripts to do some setup tasks for me including changing to the right place and then I . them, too. Btw. .sh is of course a matter of personal style. I probably wouldn't use it while installing scripts system wide. But in my ~/bin I use them to know what is what :) – Norbert Hartl May 17 '09 at 12:45
  • 14
    @lhunath Why are .sh extensions totally eww? – Carl Pritchett Dec 12 '14 at 05:59
  • 1
    @CarlPritchett [Commandname Extensions Considered Harmful](http://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful.shtml). – gniourf_gniourf Feb 28 '15 at 08:30
  • 4
    To me the reasoning in that article is bogus. It seems that the result has been known upfront but finding reasons for it wasn't that easy. So I stand my point it is a matter of personal style, nothing else. – Norbert Hartl Mar 02 '15 at 11:47
  • Using `.` is fine but if your script alters some environment varaibles this changes will persist, which is not what you want in all cases, so this approach is not always ideal – cgdave Apr 08 '16 at 10:23
  • Using . is industry standard for a de facto include notation. For this usage they "damage" (persistent changes) are restricted to the .sh they were included in. – mckenzm Mar 21 '19 at 02:55
  • Updated link: [Commandname extensions to be considered harmful](https://wooledge.org/~greybot/meta/.sh) – Peter Krebs Aug 24 '23 at 12:15
206

You need to convert your script to a shell function:

#!/bin/bash
#
# this script should not be run directly,
# instead you need to source it from your .bashrc,
# by adding this line:
#   . ~/bin/myprog.sh
#

function myprog() {
  A=$1
  B=$2
  echo "aaa ${A} bbb ${B} ccc"
  cd /proc
}

The reason is that each process has its own current directory, and when you execute a program from the shell it is run in a new process. The standard "cd", "pushd" and "popd" are builtin to the shell interpreter so that they affect the shell process.

By making your program a shell function, you are adding your own in-process command and then any directory change gets reflected in the shell process.

winden
  • 2,577
  • 1
  • 15
  • 6
  • 2
    how would you do this in tcsh? – joedborg Aug 25 '11 at 11:12
  • 4
    +1. One thing which I noticed was in the file, we have to define the function before calling it. It might help someone as inexperienced as me. – Bhushan Sep 25 '13 at 17:56
  • What's the big difference between defining a function and creating an alias? – HelloGoodbye Jun 17 '15 at 20:46
  • One big difference is that any stateful actions in an alias are evaluated once, at the time the alias is created, whereas in a function, it's evaluated when the function is called. So, for instance, if you wanted to make an alias to go to the top of the git repo you're in, and you made an alias in your `.bashrc` like: `alias repotop="cd $(git rev-parse --show-toplevel)"` that call to `git` would happen when your `.bashrc` was sourced, and never again, which is probably not what you want. Turning it into a function will evaluate the `git` root on every invocation of the function. – ipmcc Sep 29 '15 at 12:38
  • 2
    Once I've created this myprog.sh file and added it to the .bashrc how do I run the script? Should I call my function from another shell file or directly from the shell? It seems it doesn't work in neither ways... – Andrea Silvestri Mar 30 '16 at 10:55
  • 1
    @AndreaSilvestri Either log out and log back in or source .bashrc. Adding it to .bashrc doesn't do anything until .bashrc is run. – Jon Strayer Mar 29 '17 at 13:31
  • @HelloGoodbye, functions are far more flexible -- aliases are just prefix substitution of the command; functions act like, well, _functions_ -- they have their own stack frame, they can recurse, they accept arguments and have return values, they can perform conditional logic... – Charles Duffy Dec 30 '20 at 16:18
  • 1
    @winden, please avoid teaching the `function` keyword, and *especially* combining `function` with `()`s after the name. `function myfunc {` is legacy ksh syntax that bash implements for backwards compatibility; `myfunc() {` is modern POSIX syntax that all standard-compliant shells are required to support; `function myfunc() {` merges the two in a way that's incompatible with **both** legacy ksh and modern POSIX sh. See https://wiki.bash-hackers.org/scripting/obsolete – Charles Duffy Dec 30 '20 at 16:20
62

In light of the unreadability and overcomplication of answers, i believe this is what the requestor should do

  1. add that script to the PATH
  2. run the script as . scriptname

The . (dot) will make sure the script is not run in a child shell.

Zombo
  • 1
  • 62
  • 391
  • 407
51

Putting the above together, you can make an alias

alias your_cmd=". your_cmd"

if you don't want to write the leading "." each time you want to source your script to the shell environment, or if you simply don't want to remember that must be done for the script to work correctly.

MergerMan
  • 535
  • 4
  • 2
35

If you are using bash you can try alias:

into the .bashrc file add this line:

alias p='cd /home/serdar/my_new_folder/path/'

when you write "p" on the command line, it will change the directory.

akarca
  • 1,456
  • 15
  • 12
  • 2
    +1 for alias. shell function is interesting but the OP asked for a simple nav with cd. I'm guessing most people need a script like this to navigate source code branches and for that alias is sufficient – Brad Dre Apr 03 '15 at 16:15
23

If you run a bash script then it will operates on its current environment or on those of its children, never on the parent.

If goal is to run your command : goto.sh /home/test Then work interactively in /home/test one way is to run a bash interactive subshell within your script :

#!/bin/bash
cd $1
exec bash

This way you will be in /home/test until you exit ( exit or Ctrl+C ) of this shell.

philippe lhardy
  • 3,096
  • 29
  • 36
  • I thought this would fix the problem for a shell script I was running but instead it starts the shell and forgets what I wanted. – vwvan Jun 06 '14 at 16:27
  • 1
    This worked for me in mac (zsh shell) with `exec zsh` instead of `exec bash`. Thanks. – Amit Yadav Oct 27 '21 at 12:52
20

With pushd the current directory is pushed on the directory stack and it is changed to the given directory, popd get the directory on top of the stack and changes then to it.

pushd ../new/dir > /dev/null
# do something in ../new/dir
popd > /dev/null
seb
  • 1,628
  • 1
  • 10
  • 15
6

Simply go to

yourusername/.bashrc (or yourusername/.bash_profile on MAC) by an editor

and add this code next to the last line:

alias yourcommand="cd /the_path_you_wish"

Then quit editor.

Then type:

source ~/.bashrc or source ~/.bash_profile on MAC.

now you can use: yourcommand in terminal

ThangTD
  • 1,586
  • 17
  • 16
6

Add below cd line in your shellscript this:

exec $SHELL
3

I've made a script to change directory. take a look: https://github.com/ygpark/dj

Joel
  • 111
  • 2
  • 7
3

Basically we use cd.. to come back from every directory. I thought to make it more easy by giving the number of directories with which you need to come back at a time. You can implement this using a separate script file using the alias command . For example:

code.sh

#!/bin/sh
 _backfunc(){
 if [ "$1" -eq 1 ]; then
  cd ..
 elif [ "$1" -eq 2 ]; then
  cd ../..
 elif [ "$1" -eq 3 ]; then
  cd ../../..
 elif [ "$1" -eq 4 ]; then
  cd ../../../..
 elif ["$1" -eq 10]; then
  cd /home/arun/Documents/work
 fi
 }
alias back='_backfunc'   

After using source code.sh in the current shell you can use :

$back 2 

to come two steps back from the current directory. Explained in detail over here. It is also explained over there how to put the code in ~/.bashrc so that every new shell opened will automatically have this new alias command. You can add new command to go to specific directories by modifying the code by adding more if conditions and different arguments. You can also pull the code from git over here.

Arun Chettoor
  • 1,021
  • 1
  • 12
  • 17
3

This is a simplified compilation of above answer.
Create a shell file shellfile.sh In the script change your directory inside a function

#!/bin/bash

cd folder1/folder2/

Now run the script with . before it.
. uses the current thread/session to execute the script.

. shellfile.sh
Shivam Anand
  • 952
  • 1
  • 10
  • 21
2

This is my current way of doing it for bash (tested on Debian). Maybe there's a better way:

Don't do it with exec bash, for example like this:

#!/bin/bash
cd $1
exec bash

because while it appears to work, after you run it and your script finishes, yes you'll be in the correct directory, but you'll be in it in a subshell, which you can confirm by pressing Ctrl+D afterwards, and you'll see it exits the subshell, putting you back in your original directory.

This is usually not a state you want a script user to be left in after the script they run returns, because it's non-obvious that they're in a subshell and now they basically have two shells open when they thought they only had one. They might continue using this subshell and not realize it, and it could have unintended consequences.

If you really want the script to exit and leave open a subshell in the new directory, it's better if you change the PS1 variable so the script user has a visual indicator that they still have a subshell open.

Here's an example I came up with. It is two files, an outer.sh which you call directly, and an inner.sh which is sourced inside the outer.sh script. The outer script sets two variables, then sources the inner script, and afterwards it echoes the two variables (the second one has just been modified by the inner script). Afterwards it makes a temp copy of the current user's ~/.bashrc file, adds an override for the PS1 variable in it, as well as a cleanup routine, and finally it runs exec bash --rcfile pointing at the .bashrc.tmp file to initialize bash with a modified environment, including the modified prompt and the cleanup routine.

After outer.sh exits, you'll be left inside a subshell in the desired directory (in this case testdir/ which was entered into by the inner.sh script) with a visual indicator making it clear to you, and if you exit out of the subshell, the .bashrc.tmp file will be deleted by the cleanup routine, and you'll be back in the directory you started in.

Maybe there's a smarter way to do it, but that's the best way I could figure out in about 40 minutes of experimenting:

file 1: outer.sh

#!/bin/bash

var1="hello"
var2="world"

source inner.sh

echo $var1
echo $var2

cp ~/.bashrc .bashrc.tmp

echo 'export PS1="(subshell) $PS1"' >> .bashrc.tmp

cat <<EOS >> .bashrc.tmp
cleanup() {
    echo "cleaning up..."
    rm .bashrc.tmp
}

trap 'cleanup' 0
EOS

exec bash --rcfile .bashrc.tmp

file 2: inner.sh

cd testdir
var2="bird"

then run:

$ mkdir testdir
$ chmod 755 outer.sh

$ ./outer.sh

it should output:

hello
bird

and then drop you into your subshell using exec bash, but with a modified prompt which makes that obvious, something like:

(subshell) user@computername:~/testdir$

and if you Ctrl-D out of the subshell, it should clean up by deleting a temporary .bashrc.tmp file in the testdir/ directory

I wonder if there's a better way than having to copy the .bashrc file like that though to change the PS1 var properly in the subshell...

Defcronyke
  • 611
  • 1
  • 7
  • 21
0

This approach is easier for me.

Suppose on a personal iMac where you are an admin, under the default directory when a command window is opened, /Users/jdoe, this will be the directory to go to: /Users/jdoe/Desktop/Mongo/db.3.2.1/bin.

These are the steps that can have the job done:

  1. vi mongobin, in which I entered: cd /Users/jdoe/Desktop/Mongo/db.3.2.1/bin as the first line.
  2. chmod 755 mongobin
  3. source mongobin
  4. pwd

Voila!

vgoff
  • 10,980
  • 3
  • 38
  • 56
Daniel C. Deng
  • 1,573
  • 13
  • 8
0

I've also created a utility called goat that you can use for easier navigation.

You can view the source code on GitHub.

As of v2.3.1 the usage overview looks like this:

# Create a link (h4xdir) to a directory:
goat h4xdir ~/Documents/dev

# Follow a link to change a directory:
cd h4xdir

# Follow a link (and don't stop there!):
cd h4xdir/awesome-project

# Go up the filesystem tree with '...' (same as `cd ../../`):
cd ...

# List all your links:
goat list

# Delete a link (or more):
goat delete h4xdir lojban

# Delete all the links which point to directories with the given prefix:
goat deleteprefix $HOME/Documents

# Delete all saved links:
goat nuke

# Delete broken links:
goat fix
Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
-1

I like to do the same thing for different projects without firing up a new shell.

In your case:

cd /home/artemb

Save the_script as:

echo cd /home/artemb

Then fire it up with:

\`./the_script\`

Then you get to the directory using the same shell.

Chris Albert
  • 2,462
  • 8
  • 27
  • 31
DrGamma
  • 1
  • 1
-1

Declare your path:

PATH='/home/artemb'     
cd ${PATH}
PrakashG
  • 1,642
  • 5
  • 20
  • 30
m_sam
  • 1
  • 1