4

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?

Magnus
  • 1,413
  • Do you have readlink from the coreutils package? – manatwork Oct 09 '12 at 13:54
  • Yes, I do have readlink. – Magnus Oct 09 '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 09 '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 09 '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 09 '12 at 14:13
  • Thanks jw, I'll try this. Probably I'll do "export SCRIPTDIR=/home/data" in my .bash_login – Magnus Oct 09 '12 at 14:32
  • .bash_login will not work for cron-jobs. Look at the way init-scripts handle this: Use a configuration file located at a fixed directory (usually /etc/sysconfig/appname) and parse that from your script. – Nils Oct 11 '12 at 20:28

4 Answers4

2

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")
script_directory=${script_path_in_package%/*}

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.

1

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.

user17591
  • 1,098
0

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).

January
  • 1,937
0

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...

itsbruce
  • 1,794
  • 1
  • 10
  • 12