3

Inspired by this question.

Getting the source directory of a Bash script from within

Is it possible, but without the need for bash.

I'm running Ubuntu and before i needed that accepted solution from the Bash question, my hash bang was like this

#!/bin/sh

and I liked it that way, is there a way to keep it that way?

EDIT:

It should output the correct directory, even when run thus,

./script.sh
4
  • as far as I remember, /bin/sh is a symbolic link to /bin/bash. Therefore, the solution in your question is applied.
    – ariefbayu
    Commented Dec 12, 2011 at 0:02
  • @silent: not on Ubuntu, where /bin/sh is Dash, which is a much simpler shell than Bash. They changed this a year or two ago. Commented Dec 12, 2011 at 0:16
  • Ah, Ok. I don't know about that.
    – ariefbayu
    Commented Dec 12, 2011 at 1:25
  • And not on a whole bunch of other systems, where /bin/sh could be, for example, a symlink to ksh, or an actual Bourne shell. Commented Dec 12, 2011 at 1:56

6 Answers 6

4

How about $(dirname -- "$0")?

If your shell uses backticks only, it'd be:

`dirname -- "$0"`
4
  • That must be $(dirname -- "$0"), or you will run into errors.
    – jørgensen
    Commented Dec 12, 2011 at 0:20
  • Thanks, I've added the double-dash you suggest to avoid dirname interpreting the path as arguments. Commented Dec 12, 2011 at 1:17
  • dirname is not POSIX, and may not be available on all systems.
    – Daenyth
    Commented Dec 12, 2011 at 1:50
  • The OP uses Ubuntu, and dirname is surely on there. Commented Dec 13, 2011 at 1:05
3

/bin/sh is a bourne-compatible shell (today either bash or ksh). On those, the following line of code will give you the absolute path of the currently running script:

DIR=$( cd $( dirname -- "$0" ) > /dev/null ; pwd )

There is a lot of wisdom in this line:

  • The quotes make sure that spaces in the path don't cause problems.
  • No quotes are necessary to protect the results of $(...). If you use backticks, you need more quotes but almost any shell today supports $() because of the many problems with backticks.
  • The $() has the nice side effect that the commands are executed in a subshell, so the current path doesn't change.
  • The -- is there to avoid problems with paths that start with -
  • The > /dev/null is there because cd can print the current path depending on a shell option.
  • I'm using dirname because everything else needs special features which aren't available everywhere. It means that a process has to be forked but then, this happens once.
  • pwd prints the absolute path
2

It's not possible to do this in a reliable way without making assumptions about the system it's running on. This bash wiki entry goes into a lot of detail about the issue.

The page offers these alternatives:

It really makes the most sense to keep your script's configuration in a single, static location such as /etc/foobar.conf. If you need to define multiple configuration files, then you can have a directory (say, /var/lib/foobar/ or /usr/local/lib/foobar/), and read that directory's location from a fixed place such as /etc/foobar.conf. If you don't even want that much to be hard-coded, you could pass the location of foobar.conf (or of your configuration directory itself) as a parameter to the script. If you need the script to assume certain default in the absence of /etc/foobar.conf, you can put defaults in the script itself, or fall back to something like $HOME/.foobar.conf if /etc/foobar.conf is missing. When you install the script on a target system, you could put the script's location into a variable in the script itself. The information is available at that point, and as long as the script doesn't move, it will always remain correct for each installed system. In most cases, it makes more sense to abort gracefully if your configuration data can't be found by obvious means, rather than going through arcane processes and possibly coming up with wrong answers.

2
  • Hmm, thought provoking answer. Thank you.
    – bluekeys
    Commented Dec 12, 2011 at 16:02
  • Other answers below do work for most cases, but it's still important to remember that there's no foolproof way for %100 of the situations. Good reference. Commented Mar 25, 2012 at 21:45
2
mydir="${0%/*}"
if [ "$mydir" == "$0" ]; then
    echo "Indeterminate location."
    exit 1;
fi;

No need to create an extra process and call dirname. Indetermine location can happen when running it directly within the shell prompt (where $0 == "-bash", for example), or when a program is invoked by searching through $PATH. (Then $0 will also be without slashes)

#optional addition
mydir=$(readlink -f "$0")
4
  • Do you think ${0%/*} works in Dash, the Ubuntu default shell the OP uses? Commented Dec 12, 2011 at 1:16
  • Yes, that is a POSIX parameter expansions. pubs.opengroup.org/onlinepubs/009695399/utilities/…
    – jordanm
    Commented Dec 12, 2011 at 1:28
  • $0 is not reliable, and readlink is not available on every system
    – Daenyth
    Commented Dec 12, 2011 at 1:50
  • I didn't understand your answer, can you elaborate for me please?
    – bluekeys
    Commented Dec 13, 2011 at 23:50
2

Personally, I use :

EXEC_DIR=$( readlink -f $( dirname -- "$0" ) )

in my shell scripts (/bin/sh). Works OK for me so far.

1
  • @Aaron thanks for the --, I was unaware of its existence and was looking for such a feature !
    – Offirmo
    Commented Dec 14, 2011 at 15:33
2

For those who looking cross BASH DASH current script folder.

1.sh:

#!/bin/sh
if [ "$BASH_SOURCE" != "" ]
then
    DIR="$( cd "$( dirname "$BASH_SOURCE" )" && pwd )"
elif [ "$DASH_SOURCE" != "" ]
then
    DIR="$( cd "$( dirname "$DASH_SOURCE" )" && pwd )"
else
    DIR="$( cd "$( dirname "$0" )" && pwd )"
fi
echo $DIR

Will produce the following:

cd ~
docker@boot2docker:~$ sh ./test/1.sh 
/home/docker/test
docker@boot2docker:~$ ./test/1.sh 
/home/docker/test
docker@boot2docker:~$ source ./test/1.sh 
/home/docker

If anybody knows how to deal with source, please comment below.

Not the answer you're looking for? Browse other questions tagged or ask your own question.