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:
- Get the script directory (relative to the current directory)
- cd into the directory
- 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
Claudio Sabato is an IT expert with over 15 years of professional experience in Python programming, Linux Systems Administration, Bash programming, and IT Systems Design. He is a professional certified by the Linux Professional Institute.
With a Master’s degree in Computer Science, he has a strong foundation in Software Engineering and a passion for robotics with Raspberry Pi.
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)
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.
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.