1. Overview

Many times, we need to find a script’s filename from the script itself. One common use case is to show the usage of the script with the –help option.

In this tutorial, we’ll discuss some of the ways to achieve this in bash.

2. Using the basename Command

The basename command allows us to strip the directory and suffix from filenames. We can use it to find the script’s filename:

$ cat dir1/dir2/find-script-name.sh 
#!/bin/bash
echo "Script name =" $(basename "$0")

$ dir1/dir2/find-script-name.sh 
Script name = find-script-name.sh

In this example, we’re using a combination of the $0 and basename command to find the filename of the script we’ve invoked. The $0 is a special variable in bash that represents the filename with a relative path. Hence, we’ve used the basename command to strip directory names from the script’s filename.

3. Using Parameter Expansion

We can also use parameter expansion in bash to find the filename of our script:

$ cat dir1/dir2/find-script-name.sh 
#!/bin/bash
echo "Script name = ${0##*/}"

$ dir1/dir2/find-script-name.sh 
Script name = find-script-name.sh

In this example, ${0##*/} represents the parameter expansion.

4. Using the BASH_SOURCE Environment Variable

BASH_SOURCE is an environment variable in bash that contains a filename. We can use it with the basename command to get the script’s filename:

$ cat dir1/dir2/find-script-name.sh 
#!/bin/bash
echo "Script name =" $(basename "${BASH_SOURCE}")

$ /dir1/dir2/find-script-name.sh 
Script name = find-script-name.sh

On Linux, linking files to somewhere else is a common operation, particularly for executables. So far, we’ve learned three ways to find a script’s filename from the script itself through an example.

Next, let’s figure out if they still work if we invoke the script via a symbolic link.

First, let’s create a new script to contain the three approaches:

$ cat dir1/dir2/find-script-name-all.sh
#!/bin/bash
echo 'Getting name by basename $0: ' $(basename "$0")
echo 'Getting name by ${0##*/}: ' ${0##*/}
echo 'Getting name by basename ${BASH_SOURCE}: ' $(basename "${BASH_SOURCE}")

Next, let create a symbolic link to the script, and invoke the script by the link:

$ ln -s dir1/dir2/find-script-name-all.sh i-am-a-link
$ ls -l i-am-a-link 
lrwxrwxrwx 1 kent kent 33 Jan 24 23:10 i-am-a-link -> dir1/dir2/find-script-name-all.sh

$ ./i-am-a-link 
Getting name by basename $0:  i-am-a-link
Getting name by ${0##*/}:  i-am-a-link
Getting name by basename ${BASH_SOURCE}:  i-am-a-link

As we can see from the output above, all three approaches have reported the link’s name instead of the real script name. That is, all approaches failed.

Next, let’s see how to solve this problem.

We can use the readlink command to solve this problem. Let’s add two new commands to the find-script-name-all.sh script:

$ cat dir1/dir2/find-script-name-all.sh 
#!/bin/bash
echo 'Getting name by basename $0: ' $(basename "$0")
echo 'Getting name by ${0##*/}: ' ${0##*/}
echo 'Getting name by basename ${BASH_SOURCE}: ' $(basename "${BASH_SOURCE}")
echo '------ Using readlink ----- '
echo 'Getting name by basename $(readlink -f ${BASH_SOURCE}): ' $(basename "$(readlink -f "${BASH_SOURCE}")")
echo 'Getting name by basename $(readlink -f $0): ' $(basename "$(readlink -f "$0")")

Here, we’ve used the readlink command with the -f option to follow every symbolic link recursively.

Now, let’s invoke our script by the link again, and see if we can get the real script filename:

$ ./i-am-a-link 
Getting name by basename $0:  i-am-a-link
Getting name by ${0##*/}:  i-am-a-link
Getting name by basename ${BASH_SOURCE}:  i-am-a-link
------ Using readlink ----- 
Getting name by basename $(readlink -f ${BASH_SOURCE}):  find-script-name-all.sh
Getting name by basename $(readlink -f $0):  find-script-name-all.sh

As the test above shows, the new commands with readlink can report the script’s real filename.

Further, it’s worth mentioning that if we invoke the script directly, the readlink approaches work as well:

$ dir1/dir2/find-script-name-all.sh 
Getting name by basename $0:  find-script-name-all.sh
Getting name by ${0##*/}:  find-script-name-all.sh
Getting name by basename ${BASH_SOURCE}:  find-script-name-all.sh
------ Using readlink ----- 
Getting name by basename $(readlink -f ${BASH_SOURCE}):  find-script-name-all.sh
Getting name by basename $(readlink -f $0):  find-script-name-all.sh

Therefore, the readlink approaches are general solutions to the problem.

7. Conclusion

In this article, we addressed some of the practical ways to get a script’s filename from within the script itself.

Further, we discussed the scenario when a script is executed through a symbolic link.