标签云

微信群

扫码加入我们

WeChat QR Code

在获得一个bash脚本的源目录

How do I get the path of the directory in which a Bash script is located, inside that script?

For instance, let's say I want to use a Bash script as a launcher for another application. I want to change the working directory to the one where the Bash script is located, so I can operate on the files in that directory, like so:

$ ./application


None of the current solutions work if there are any newlines at the end of the directory name - They will be stripped by the command substitution. To work around this you can append a non-newline character inside the command substitution - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - and remove it without a command substitution - DIR="${DIR%x}".

2018年05月23日36分48秒

jpmc26 There are two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'.

2018年05月23日36分48秒

anyone who lets people sabotage their system in that way shouldn't leave it up to bash to detect such problems... much less hire people capable of making that kind of mistake. I have never had, in the 25 years of using bash, seen this kind of thing happen anywhere.... this is why we have things like perl and practices such as taint checking (i will probably be flamed for saying that :)

2018年05月23日36分48秒

l0b0 Consider that you'd need the same protection on dirname, and that the directory could start with a - (e.g. --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Perhaps this is overkill?

2018年05月23日36分48秒

I stronly suggest to read this Bash FAQ about the subject.

2018年05月23日36分48秒

You can fuse this approach with the answer by user25866 to arrive at a solution that works with source <script> and bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".

2018年05月23日36分48秒

Sometimes cd prints something to STDOUT! E.g., if your $CDPATH has .. To cover this case, use DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"

2018年05月23日36分48秒

Wait, so what is the final command to use?

2018年05月23日36分48秒

This accepted answer is not ok, it doesn't work with symlinks and is overly complex. dirname $(readlink -f $0) is the right command. See gist.github.com/tvlooy/cbfbdb111a4ebad8b93e for a testcase

2018年05月23日36分48秒

tvlooy IMO your answer isn't exactly OK as-is either, because it fails when there is a space in the path. In contrast to a newline character, this isn't unlikely or even uncommon. dirname "$(readlink -f "$0")" doesn't add complexity and is fair measure more robust for the minimal amount of trouble.

2018年05月23日36分48秒

For portability beyond bash, $0 may not always be enough. You may need to substitute "type -p $0" to make this work if the command was found on the path.

2018年05月23日36分48秒

Darron: you can only use type -p if the script is executable. This can also open a subtle hole if the script is executed using bash test2.sh and there is another script with the same name executable somewhere else.

2018年05月23日36分48秒

Darron: but since the question is tagged bash and the hash-bang line explicitly mentions /bin/bash I'd say it's pretty safe to depend on bashisms.

2018年05月23日36分48秒

+1, but the problem with using dirname $0 is that if the directory is the current directory, you'll get .. That's fine unless you're going to change directories in the script and expect to use the path you got from dirname $0 as though it were absolute. To get the absolute path: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (But surely there's a better way to expand that path?)

2018年05月23日36分48秒

T.J. Crowder I'm not sure sure dirname $0 is a problem if you assign it to a variable and then use it to launch a script like $dir/script.sh; I would imagine this is the use case for this type of thing 90% of the time. ./script.sh would work fine.

2018年05月23日36分48秒

readlink will not availabe in some platform in default installation. Try to avoid using it if you can

2018年05月23日36分48秒

be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

2018年05月23日36分48秒

In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. Thanks

2018年05月23日36分48秒

In OSX, there is greadlink, which is basically the readlink we are all familiar. Here is a platform independent version: dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`

2018年05月23日36分48秒

Good call, robert. FYI, greadlink can easily be installed through homebrew: brew install coreutils

2018年05月23日36分48秒

Nice! Could be made shorter replacing "pushd[...] popd /dev/null" by SCRIPT_PATH=readlink -f $(dirname "${VIRTUAL_ENV}");

2018年05月23日36分48秒

And instead of using pushd ...; would not it be better to use $(cd dirname "${SCRIPT_PATH}" && pwd)? But anyway great script!

2018年05月23日36分48秒

Isn't the if redundant? while is testing the same thing...

2018年05月23日36分48秒

It's dangerous for a script to cd out of its current directory in the hope of cding back again later: The script may not have permission to change directory back to the directory that was current when it was invoked. (Same goes for pushd/popd)

2018年05月23日36分48秒

readlink -f is GNU-specific. BSD readlink does not have that option.

2018年05月23日36分48秒

It won't work if you source the script. "source my/script.sh"

2018年05月23日36分48秒

then nothing will

2018年05月23日36分48秒

I use this all the time in my bash scripts that automate stuff and often invoke other scripts in the same dir. I'd never use source on these and cd $(dirname $0) is easy to remember.

2018年05月23日36分48秒

works for /bin/sh

2018年05月23日36分48秒

vidstige: ${BASH_SOURCE[0]} instead of $0 will work with source my/script.sh

2018年05月23日36分48秒

When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo.

2018年05月23日36分48秒

also works with source / . operator!

2018年05月23日36分48秒

with your solution, invoking the script like ./script.sh shows . instead of the full directory path

2018年05月23日36分48秒

There's no -f option for readlink on MacOS. Use stat instead. But still, it shows . if you are in 'this' dir.

2018年05月23日36分48秒

This is the best solution on Ubuntu 16.04

2018年05月23日36分48秒

But not on mac bash!

2018年05月23日36分48秒

You can do it all in one line like this: DIRECTORY=$(cd dirname $0 && pwd)

2018年05月23日36分48秒

This doesn't work if the script sources another script and you want to know the name of the latter.

2018年05月23日36分48秒

If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example.

2018年05月24日36分48秒

This is way shorter than the chosen answer. And appears to work just as well. This deserves 1000 votes just so people do not overlook it.

2018年05月23日36分48秒

As many of the previous answers explain in detail, neither $0 nor pwd are guaranteed to have the right information, depending on how the script is invoked.

2018年05月23日36分48秒

It's also bash specific, but perhaps bash's behavior has changed? /proc/fd/$$/255 seems to point to the tty, not to a directory. For example, in my current login shell, file descriptors 0, 1, 2, and 255 all refer to /dev/pts/4. In any case, the bash manual doesn't mention fd 255, so it's probably unwise to depend on this behavior.\

2018年05月23日36分48秒

Interactive shell != script. Anyway realpath ${BASH_SOURCE[0]}; would seem to be the best way to go.

2018年05月23日36分48秒

I had success with this when running a script by itself or by using sudo, but not when calling source ./script.sh

2018年05月23日36分48秒

And it fails when cd is configured to print the new path name.

2018年05月23日36分48秒

This should be $(dirname "$(readlink -f "$BASH_SOURCE")") to protect against spaces.

2018年05月23日36分48秒

Doesn't work if the script is being sourced from another script.

2018年05月23日36分48秒

This is the real one! Works with simple sh too! Problem with simple dirname "$0" based solutions: If the script is in the $PATH and is invoked without path, they will give wrong result.

2018年05月23日36分48秒

Notinlist Not so. If the script is found via the PATH, $0 will contain the absolute filename. If the script is invoked with a relative or absolute filename containing a /, $0 will contain that.

2018年05月23日36分48秒

This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you!

2018年05月23日36分48秒

Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }

2018年05月23日36分48秒

konsolebox, what are you trying to defend against? I'm generally a fan of inlining logical conditionals, but what was the specific error that you were seeing in the pushd? I'd match rather find a way to handle it directly instead of returning an empty SCRIPT_DIR.

2018年05月23日36分48秒

Fuwjax Natural practice to avoid doing popd in cases (even when rare) where pushd fails. And in case pushd fails, what do you think should be the value of SCRIPT_DIR? The action may vary depending on what may seem logical or what one user could prefer but certainly, doing popd is wrong.

2018年05月23日36分48秒

It breaks if you source or . the script. In those situations, $_ would contain the last parameter of the last command you ran before the .. $BASH_SOURCE works every time.

2018年05月23日36分48秒

I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop.

2018年05月23日36分48秒

DanDascalescu Using the one-liner? Or the full code snippet at the bottom? And were you feeding it any tricky pathnames?

2018年05月23日36分48秒

The one line plus another line to echo $resolved, I saved it as d, chmod +x d, ./d.

2018年05月23日36分48秒

DanDascalescu The first line in your script needs to be #!/bin/bash

2018年05月23日36分48秒

DanDascalescu See stackoverflow.com/a/12296783/558709

2018年05月23日36分48秒

All I want to know is, why this way is not good? It seemed no bad and correct for me. Could anyone explain why it's downvoted?

2018年05月23日36分48秒

realpath is not a standard utility.

2018年05月23日36分48秒

On Linux, realpath is a standard utility (part of the GNU coreutils package), but it is not a bash built-in (i.e., a function provided by bash itself). If you're running Linux, this method will probably work, although I'd substitute the $0 for ${BASH_SOURCE[0]} so that this method will work anywhere, including in a function.

2018年05月23日36分48秒

The order of the operations in this answer is wrong. You need to first resolve the symlink, then do dirname because the last part of $0 may be a symlink that points to a file that is not in the same directory as the symlink itself. The solution described in this answer just gets the path of the directory where the symlink it stored, not the directory of the target. Furthermore, this solution is missing quoting. It will not work if the path contains special characters.

2018年05月23日36分48秒

unless the script was sourced with . or 'source' in which case it will still be whatever script sourced it, or, if from the command line, '-bash' (tty login) or 'bash' (invoked via 'bash -l') or '/bin/bash' (invoked as an interactive non-login shell)

2018年05月23日36分48秒

I added second pair of quotes around dirname call. Needed if the directory path contains spaces.

2018年05月23日36分48秒

I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop, instead of the actual directory I run the script from.

2018年05月23日36分48秒

The point on which is very debatable. type, hash, and other builtins do the same thing better in bash. which is kindof more portable, though it really isn't the same which used in other shells like tcsh, that has it as a builtin.

2018年05月23日36分48秒

"Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell.

2018年05月23日36分48秒

this is giving me the directory with the last entry stripped off, i.e., the path to the container of the container of the script.

2018年05月23日36分48秒

Please explain more about the realpath function.

2018年05月23日36分48秒

Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix).

2018年05月23日36分48秒

Your cross-compatible solution doesn’t work when the script is symlinked.

2018年05月23日36分48秒

You probably should add slash to that: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". You'd have problems with root directory if you don't. Also why do you even have to use echo?

2018年05月23日36分48秒