55

I have a bash script file, which is put under some directory added to $PATH so that I can call the script from any directory.

There is another text file under the same directory as the script. I wonder how to refer to the text file in the script?

For example, if the script is just to output the content of the text file, cat textfile won't work, since when calling the script from a different directory, the text file is not found.

Caleb
  • 70,105
Tim
  • 101,790

7 Answers7

43

These should work the same, as long as there are no symlinks (in the path expansion or the script itself):

  • MYDIR="$(dirname "$(realpath "$0")")"

  • MYDIR="$(dirname "$(which "$0")")"

  • A two step version of any of the above:

    MYSELF="$(realpath "$0")"

    MYDIR="${MYSELF%/*}"

If there is a symlink on the way to your script, then which will provide an answer not including resolution of that link. If realpath is not installed by default on your system, you can find it here.

[EDIT]: As it seems that realpath has no advantage over readlink -f suggested by Caleb, it is probably better to use the latter. My timing tests indicate it is actually faster.

  • No problem. By the way where does realpath come from on your system. (For others that don't have it you can use readlink -f – Caleb Aug 01 '11 at 10:27
  • @Caleb Actually I thought it belonged to the set of standard GNU utilities (coreutils), but I can see now it is a separate package. – rozcietrzewiacz Aug 01 '11 at 10:48
  • @rozcietrzewiacz realpath dates back from before readlink -f (and even readlink, IIRC) was in GNU coreutils (there were several similar tools around. readlink -f eventually became the de facto standard); realpath is only kept for compatibility with scripts that still use it. – Gilles 'SO- stop being evil' Aug 01 '11 at 21:38
  • Whats the advantage of $(dirname "$(which "$0")") over $(dirname $0) where the which is no longer present? Isn't it the same? – UlfR Oct 22 '15 at 08:34
  • 2
    readlink -f does not seem to work on Mac OS X 10.11.6, but realpath works out of the box. – Grav Mar 10 '17 at 12:28
  • @UlfR the which is unnecessary if you call your script with a relative path, e.g. ./a-dir/my-script, but it is required if you put your script somewhere on your $PATH and then called it from an unrelated directory... my-script would then be expanded to it's full path before passing to dirname. – Matt Sep 18 '19 at 08:48
  • Just to confuse matters even more: The man page for readlink suggests that realpath is the preferred command, and it seems realpath(1) is planned to be future POSIX: https://man.netbsd.org/realpath.1 – 00prometheus Jan 31 '23 at 14:21
19

My systems do not have realpath as suggested by rozcietrzewiacz.

You can accomplish this using the readlink command. The advantage of using this over parsing which or other solutions is that even if a part of the path or the filename executed was a symlink, you would be able to find the directory where the actual file was.

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

Your text file could then be read into a variable like this:

TEXTFILE="$(<$MYDIR/textfile)"
Caleb
  • 70,105
  • @rozcietrzewiacz: I actually wasn't refering to just your which suggestion. The normal solution for this involves either just dirname or a combination of cd and pwd in a subshell. Readlink has the advantage here. realpath seems to pretty much be just a wrapper for readlink -f anyway. – Caleb Aug 01 '11 at 11:07
  • I don't know how realpath is different from readlink -f. I can only see it gives me the same results (as opposed to which). – rozcietrzewiacz Aug 01 '11 at 11:21
  • Keep in mind that readlink -f (from GNU coreutils) does NOT require the last element of the path to exist, readlink -e does, but isn't supported by busybox readlink, which mimics the behavior of -e in their -f option. – dragon788 Oct 01 '18 at 20:33
14

$0 in the script will be the full path to the script, and dirname will take a full path and give you just the directory, so you can do this to cat textfile:

$ cat "$(dirname -- "$0")/textfile"
Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
5

You can put this at the top of your script:

cd "${BASH_SOURCE%/*}" || exit

The BASH_SOURCE internal bash variable is actually an array of pathnames. If you expand it as a simple string, e.g. "$BASH_SOURCE", you'll get the first element, which is the pathname of the currently executing function or script.

Source: http://mywiki.wooledge.org/BashFAQ/028

4

I was trying these and realpath didn't work for me. The solution I went with is:

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

Which has worked well so far. I would like to know if there are any potential issues with the approach.

Brettski
  • 141
  • 1
    Good, but does not follow symlinks. Try this: SCRIPT_DIR="$( cd "$(dirname "$( readlink -f ${BASH_SOURCE[0]} )")" >/dev/null 2>&1 && pwd)" – OronNavon Aug 25 '19 at 11:06
1

I use:

#! /bin/sh -
dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P) || exit
dosomethingwith "${dir%/}/some-file"

Which is POSIX and should work as long as the dirname of $0 doesn't end in newline characters, is not - and $CDPATH is not set (and possibly a few other corner cases if the script was not looked-up in $PATH).

0

I always use which to find the full path of an executable from the PATH. For instance:

which python

If you combine this with the dirname command then you get:

wp=`which python`
dn=`dirname $wp`
ls $dn