I have a bash script that works with relative paths. It needs to have a working directory that is equal to the directory the script is stored in. This works fine as long as I start it from the prompt, because I can cd to the script's directory first. However, when I create a symlink to the script in /etc/cron.hourly, the script breaks.

I need a way to make the bash script change directory to the directory the script is stored in. However, so far I am unsuccessful. Is there an easy way to do this, even if the script is called from cron via a symlink?

  • Do you have readlink from the coreutils package? – manatwork Oct 9 '12 at 13:54
  • Yes, I do have readlink. – Magnus Oct 9 '12 at 13:59
  • 2
    This is precisely why a script can not and should not rely on "its location". Bash FAQ 28 has some more explanation of why scripts don't have any such thing as "a location". If you require a specific working directory, you can: (1) hard-code that path in the script itself; (2) read it from a config file at a known location; or (3) pass it in as an argument or environment variable. – jw013 Oct 9 '12 at 13:59
  • jw013: The reason I want to do it this way is because the entire directory hierarchy is backed up on multiple systems. So I use relative paths within the script, because it may be located in /home/data in one case or /raid/archive/data in another case. – Magnus Oct 9 '12 at 14:06
  • 1
    @Magnus so use option (2) or (3) - get the root portion (/home or /raid/archive) from a config file or pass it in as an argument. There are other options too - for example, you could put the cd right in your crontab. – jw013 Oct 9 '12 at 14:13

You should be able to do this.

  • The $0 parameter expands to the full path (including name) of the script as it was invoked.
  • The built-in shell function test (or [ ]) can test if a path is a symlink using -L
  • ls -l, if applied to a symlink, will give a single line of output with the true path as the final field.

That's enough for you to discover the location of the source. Of course, that might be a symlink, which would require you to do this recursively...

  • 2
    What kind of ln does what you mentioned in the 3. point? My ln on Linux from the coreutils package not. It displays an error message : “ln: failed to create symbolic link './bar': File exists”. (Where “bar” is symlink to “foo”, either regular file or directory.) – manatwork Oct 9 '12 at 14:14
  • Whoops! Meant ls -l. Typo corrected. – itsbruce Oct 9 '12 at 14:56
  • 2
    Parsing ls is not a good idea. – jw013 Oct 9 '12 at 15:03
  • 1
    None of this is a good idea. I'd have thought that highlighting the need for recursion would have been the clue to that. – itsbruce Oct 9 '12 at 18:12

I don't recall where I got this, so I can properly attribute, but I've used this for a long time to get the "canonical" path to the script I'm executing. By canonical, I mean absolute path with no "." or ".." in it.

CANON=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")

If I need to, I'll use basename and dirname on $CANON to get those values.


The shell always knows the path to the script under normal circumstances, because it's had to read it. Note that there are circumstances where this is not true, for example with setuid scripts on the OSes that support them (not Linux) — but setuid shell scripts are a bad idea anyway. Most importantly, the script may have been moved between the time the shell opened it and the time you try to do anything with it. So only use the path to the script when there is no security or reliability concern in doing so. A software package with a known structure is a valid use case.

The path to the script that was passed to the shell interpreter is available in the special parameter $0. This may be a relative path, so if you're going to use it, do that before changing to another directory. You can use it if you know that the script won't have been moved in the meantime.

Since $0 may be a symbolic link, you'll need to get its target. There's no POSIX way to do this, but on Linux (GNU coreutils or BusyBox) and recent BSD you can use readlink -f to get a canonical absolute path.

script_path_in_package=$(readlink -f -- "$0")

Note that this restricts the ways in which you can structure your package. For example, you can't put the script in an architecture-independent directory tree, and link to it inside every architecture-specific directory tree.


The variable $0 holds the name of the program. Do

cd $(dirname $( readlink $0) )

to change to the directory in which the program resides.

Yes, $0 can be problematic, if used in a general situation. I think that using it for ones own work in a particular situation where the set up is clearly defined is fine.

In particular, the script should not use its directory for work anyways, should rely on an environmental variable or a config file, so why care about details like "someone might have moved the script in the time since you have started it" or how ksh will interpret it (given that we know that this is a bash script).

  • 1
    This is not at all reliable or standard - see Bash FAQ 28. – jw013 Oct 9 '12 at 14:04
  • 2
    If you ran a symlink rather than the original script, $0 will be the path to the symlink, not the original. – itsbruce Oct 9 '12 at 14:08
  • @itsbruce: yep, you are right! – January Oct 9 '12 at 14:17
  • @jw013 I think that this is a moot point given this particular setup. I tried to explain it now in the answer. – January Oct 9 '12 at 14:26
  • readlink $0 will not produce output, if the script is not a symlink. Use readlink -f. OTOH instead of $0, you could also use $BASH_SOURCE. Yes, that is bash specific. – pihentagy Mar 12 '14 at 13:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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