How to Get the Directory of a Bash Script

Have you ever tried to get the directory of your Bash script programmatically?

It’s a common thing people ask and it can be done in different ways.

The first thing to look at to solve this problem is the $0 variable, used in Bash to store the first element of the command executed.

Create a script called get_script_dir.sh in the directory /opt/scripts/:

#!/bin/bash

echo "$0"

If we execute it:

  • From the script directory (/opt/scripts/)
  • Using the relative path from the parent directory /opt
  • Using the absolute path of the script

We get the following:

1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 ~]$ ./test.sh 
./test.sh

2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh 
scripts/get_script_dir.sh

3) Absolute path
[ec2-user@ip-172-12-20-120 ~]$ /opt/scripts/get_script_dir.sh 
/opt/scripts/get_script_dir.sh

So this is not enough because only in the scenario 3) we get the full path for the script.

First of all, to find a solution we need to introduce the dirname command that strips the last component from a file name. We update our script to add dirname $0:

#!/bin/bash

echo "$0"
dirname "$0"

And here is the output of the script in the three scenarios shown before:

1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$ ./get_script_dir.sh 
./get_script_dir.sh
.

2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd ..  
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh 
scripts/get_script_dir.sh
scripts

3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh 
/opt/scripts/get_script_dir.sh
/opt/scripts

So the dirname allows, in scenario 3), to get the directory of the script.

Obviously, this approach doesn’t work well, we want a command that gives back the full path of the script when it’s executed from any directory (script directory, relative path, and absolute path).

Let’s also test how $0 behaves with the source command, very common in Bash scripting to execute the lines in a script:

[ec2-user@ip-172-12-20-120 scripts]$ source get_script_dir.sh 
-bash

This time we don’t get the path of the script back but just -bash.

This doesn’t work in the way we want, we need to find an alternative.

The BASH_SOURCE array

The $BASH_SOURCE array represents an alternative to $0 that is a lot more robust.

I don’t want to get into too many details about $BASH_SOURCE that can be quite confusing at this stage.

The only thing that matters right now is that $BASH_SOURCE always contains the name and path of the script executed. As we have seen before this is true for $0 only when the script is not sourced (using the source command).

With $BASH_SOURCE our script becomes:

#!/bin/bash

echo "${BASH_SOURCE[0]}"
dirname "${BASH_SOURCE[0]}"

Where ${BASH_SOURCE[0]} is the first element of the BASH_SOURCE array.

Let’s see how it behaves in the three scenarios we have analysed before:

1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$ ./get_script_dir.sh 
./get_script_dir.sh
. 

2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd ..  
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh 
scripts/get_script_dir.sh
scripts 

3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh 
/opt/scripts/get_script_dir.sh
/opt/scripts

So there are no changes compared to $0…so what’s the point of using BASH_SOURCE?

What happens if we source our script in the same way we have done before?

[ec2-user@ip-172-12-20-120 scripts]$ source get_script_dir.sh 
get_script_dir.sh
.

That’s great!

This time we get the correct output instead of -bash (see example in the previous section using source and $0).

A One Liner to Get the Script Directory

Now that we know that it’s better to use the $BASH_SOURCE variable we want to come up with a generic way to get the directory of the script…

…no matter the location the script is executed from.

We can use the following approach:

  1. Get the script directory (relative to the current directory)
  2. cd into the directory
  3. Use pwd to get the absolute path

A script that follows the three steps above would look like:

#!/bin/bash
   
# Step 1
SCRIPT_RELATIVE_DIR=$(dirname "${BASH_SOURCE[0]}") 

# Step 2
cd $SCRIPT_RELATIVE_DIR 

# Step 3
pwd

And now let’s go through the three scenarios we have seen before:

1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$ ./get_script_dir.sh 
/opt/scripts 

2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd ..  
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh 
/opt/scripts  

3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh 
/opt/scripts/get_script_dir.sh
/opt/scripts

Finally something that works well…

It doesn’t work if every scenario (e.g. with symlinks) but it’s enough to cover most use cases.

Now we want to create a one liner to put the three steps together:

Step 1 + Step 2
cd $(dirname "${BASH_SOURCE[0]}")

Step 1 + Step 2 + Step 3
cd $(dirname "${BASH_SOURCE[0]}") && pwd

And to store this directory into a variable called SCRIPT_DIR we use:

SCRIPT_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)

The pwd command is executed only if cd $(dirname "${BASH_SOURCE[0]}") is successful.

How do we do that? Using &&.

Then you can use the echo command to print the value of $SCRIPT_DIR.

Conclusion

I thought getting the directory of a Bash script would be super simple!

As we have seen in this article it’s not the case…

Now you know how to use $0 and $BASH_SOURCE.

Also how to run a command if the execution of the previous command is successful using &&.

I hope you have found this useful…

How will you use this knowledge?

Are you writing a Bash script right now in which you need to get the directory of your script?

Let me know! 😀


Related FREE Course: Decipher Bash Scripting

3 thoughts on “How to Get the Directory of a Bash Script”

  1. Very nice. You can extend this to handle things like resolving symlinked paths to their true location, files which are symlinks themselves and so forth with a more complex version that is still a one liner (compatible with linux and mac):

    HERE=$(cd “$(dirname “$BASH_SOURCE”)”; cd -P “$(dirname “$(readlink “$BASH_SOURCE” || echo .)”)”; pwd)

    Reply
  2. yes, very useful. I needed to know what the script directory was in order to invoke a second script that lives in that directory from the first script.

    Reply
  3. It’s best to quote the “cd” command’s argument too, to avoid errors in case of a path containing spaces or other special characters (as my path contains spaces in my very case right now) like so:

    cd “$(dirname “${BASH_SOURCE[0]}”)”

    Thanks for the detailed explanation. I also thought it was easier.

    Reply

Leave a Comment