标签云

微信群

扫码加入我们

WeChat QR Code

对于一个bash脚本到本身的完整路径的可靠途径?[重复]

This question already has an answer here:

I have a bash script that needs to know its full path. I'm trying to find a broadly-compatible way of doing that without ending up with relative or funky-looking paths. I only need to support bash, not sh, csh, etc.

What I've found so far:

  1. The accepted answer to "Getting the source directory of a Bash script from within" addresses getting the path of the script via dirname $0, which is fine, but that may return a relative path (like .), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still, dirname will be part of the puzzle.

  2. The accepted answer to "Bash script absolute path with OSX" (OS X specific, but the answer works regardless) gives a function that will test to see if $0 looks relative and if so will pre-pend $PWD to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script is t in the directory /usr/bin and you're in /usr and you type bin/../bin/t to run it (yes, that's convoluted), you end up with /usr/bin/../bin as the script's directory path. Which works, but...

  3. The readlink solution on this page, which looks like this:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    But readlink isn't POSIX and apparently the solution relies on GNU's readlink where BSD's won't work for some reason (I don't have access to a BSD-like system to check).

So, various ways of doing it, but they all have their caveats.

What would be a better way? Where "better" means:

  • Gives me the absolute path.
  • Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.)
  • Relies only on bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.).
  • Avoids calling external programs if possible (e.g., prefers bash built-ins).
  • (Updated, thanks for the heads up, wich) Doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).


Please see BashFAQ/028.

2018年06月19日04分23秒

The link in solution #3 above is dead. Anyone have an updated one?

2018年06月18日04分23秒

$(readlink -f $0) - doesn't work on Mac OS 10.9.2

2018年06月19日04分23秒

use GNU readlink via homebrew to replace the BSD one

2018年06月18日04分23秒

(1.) the link you give in your own question has about 10x question-upvotes, 10x favorites, >15x answer-upvotes. (2.) Your summary is somewhat disingenious. (The link you gave has a first revision answer of "DIRECTORY=$(cd dirname $0 && pwd)" ... which does not match your summary "getting the path of the script via dirname $0"and does not as you say "return a relative path".)

2018年06月18日04分23秒

still no way to tell what the name of the file itself is though ?

2018年06月18日04分23秒

This does not work if the script is in a directory from the $PATH and you call it bash scriptname. In such a case $0 does not contain any path, just scriptname.

2018年06月19日04分23秒

I agree with pabouk, I am launching my shell script from Python and this SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" is returning me nothing

2018年06月19日04分23秒

-1 (please don't take it personnaly, it is just so that the real answer cat maybe get closer to the top): I used to do a similar thing (I used: "$(cd -P "$(dirname "$0")" && pwd)" until today, but Andrew Norrie's answer covers more cases (ie : PATH="/some/path:$PATH" ; bash "script_in_path" : will only work with his answer, not with yours (as $0 contains only "script_in_path" and no indication of where (in $PATH) bash found it). correct is : ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" (ie, andrew-norrie 's answer: covers all cases, imo)

2018年06月18日04分23秒

I just ran which realpath on Ubuntu 12.04, and it returned nothing. I was able to sudo apt-get install realpath, and now it's there, but this is something to consider if you want to include this in a script that "just works" for anyone.

2018年06月19日04分23秒

OSX 10.8 no any realpath

2018年06月18日04分23秒

Using realpath, and even shorter: SCRIPT_PATH=$(dirname $(realpath -s $0))

2018年06月19日04分23秒

realpath is part of coreutils project. It's ridiculous Ubuntu repackages a part of coreutils into a separate package.

2018年06月18日04分23秒

Many if not most of these answers are buggy when given filenames with whitespace. SCRIPT_PATH=$(dirname "$(realpath -s "$0")") is a fixed version of GuySoft's answer, though using $BASH_SOURCE would be more reliable still than $0 (and this depends on having realpath, a non-POSIX tool, installed).

2018年06月19日04分23秒

This doesn't seem to work if the originally-specified file is a symlink. I think you need to use something like readlink (or ls) in a loop, to make sure you've found a final non-symlink file. I've been on the lookout for something more concise, but in any case you can find the last version of the solution I used in the Android codebase, under dalvik/dx/etc/dx.

2018年06月19日04分23秒

danfuzz see Dennis Williamson comment, regarding using -P for pwd. It should do what you want.

2018年06月19日04分23秒

over_optimistic I'm not convinced that -P helps here: If $0 names a symlink, then cd $(dirname $0); pwd -P still just tells you what directory the symlink is in and not the physical directory where the actual script resides. You really need to use something like readlink on the script name, except that readlink is not actually POSIX and it does seem to vary in practice between OSes.

2018年06月19日04分23秒

danfuzz: linux.die.net/man/8/symlinks looks like a good thing to use, to both have "cleaner" symlinks, and find their full path equivalent

2018年06月19日04分23秒

+1 works nicely in all reasonable scenarios on a mac. No external dependencies and executes in 1 line. I use it to get the script's directory like so: SCRIPTPATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)

2018年06月19日04分23秒

As stated elsewhere, and it's not quite an edge case I think, but readlink -f is not a standard parameter and very well not be available, e.g. on my BSD.

2018年06月19日04分23秒

I ran into a corner case where this didn't work. I had a script under ~/scripts/myscript and a symlink at ~/bin/myscript which pointed to ../scripts/myscript. Running ~/bin/myscript from ~/ caused it to think the script's location was ~/. The solution here worked fine, which looks pretty similar to your solution

2018年06月19日04分23秒

Simple, and that works for me. Nice solution.

2018年06月19日04分23秒

dirname ./myscript returns .. This might not be what you want.

2018年06月19日04分23秒

BobbyNorton Yes because the non-directory suffix at that point is simply .. However, if you run which on the script name and store it in a variable, such as a=$(which ./myscript), it will return the full path, such as /tmp/myscript, which if passed to dirname will return the path. Interestingly if you run which ./myscript and not assign it to a variable, it simply returns ./myscript. I suspect this is because when assigning it to a variable it executes in another shell and passes the complete path to bash at that time.

2018年06月19日04分23秒

Unfortunately, this doesn't appear to work on OS X. It would be a nice solution if it did!

2018年06月19日04分23秒

Matt I would not use the 'which' command unless the command is in the current path. Which only searches for the command in the path. If your script is not in the path it will never be found.

2018年06月19日04分23秒

The OP already noted this solution and disregarded it for not being POSIX -- but this is nice and tidy for GNU-based systems at least. The key feature of this is that it resolves symbolic links.

2018年06月18日04分23秒

The full path provided is exactly what I was looking for. Compact, efficient, reliable. Well done!

2018年06月18日04分23秒

Not all implementation of readlink have -m I believe the OP is looking for a solution that does not depend on GNU readlink extended functionality.

2018年06月18日04分23秒

Thank you Ben. New to the site (as a contributor) and I have amended the entry as required. I didn't think that there were any commercial issues here since we are giving the code away without restriction. We also see it as a good learning tool and have documented it thoroughly for that purpose.

2018年06月19日04分23秒

Thanks Asym; much appreciated.

2018年06月19日04分23秒

Unfortunately, the symlink resolution in the updated get_realpath doesn't work for the last (basename) part of the input path, only the earlier (dirname) parts. I opened an issue for this in the Github repo. Hopefully there is a solution for this so that we can get a pure bash equivalent of readlink -f. github.com/AsymLabs/realpath-lib/issues/1

2018年06月19日04分23秒

Mikael Auno You have supplied an excellent test case. We've picked up the discussion at github and will have look at it.

2018年06月19日04分23秒

Why do we need while read a part? Why don't just cd $(dirname $a) && pwd?

2018年06月18日04分23秒

Because $( ... ) won't work in no-bash shell. I saw bad script that is using $( ... ) expression and begins #!/bin/sh manytimes. I recommend to write #!/bin/bash at beginning or stop using $( ... ) expression. This example for 1st recommendation.

2018年06月19日04分23秒

Sorry for mistake. "This example is for 2nd recommendation"

2018年06月19日04分23秒

Neither of your methods can follow recursive symbolic links

2018年06月19日04分23秒

BASH_SOURCE seems to be necessary for running the script using PATH=/path/to/your/script:$PATH yourscript.sh. Unfortunately, this needs bash

2018年06月18日04分23秒

Thanks. Re the linked question, it still relies on changing the directory and using pwd, which is what I find clunky about my solution. The regex is interesting. I can't help but worry about edge cases, though.

2018年06月19日04分23秒

Yes, it would need some good testing, but as far as I'm aware there is no portable one-shot solution that will canonicalize a name for you.

2018年06月18日04分23秒

Oh and of course the regexing will only work on *nix, it'll bomb on a cygwin environment or such.

2018年06月19日04分23秒

$(dirname calls dirname(1).

2018年06月18日04分23秒

I get bash: currentDir: command not found

2018年06月19日04分23秒

This will only resolve the symlink of directories. It will fail to resolve the symlink for a path. Consider when $0 is a link to a script in the same directory.

2018年06月19日04分23秒

readlink does not work like this on BSD (and hence on OS X) and the question was referring to an alternative.

2018年06月19日04分23秒

On Linux CentOS 7, this works fine when the script is executed with the full path (/home/me/script.sh) but returns just . when the script is executed with sh script.sh or ./script.sh

2018年06月18日04分23秒

This seems to work for me, I think it may have been downvoted for not having any additional commentary that explains how it works.

2018年06月19日04分23秒

FWIW I like the conciseness of this solution and it works out of box on Ubuntu 16.04 LTS.

2018年06月19日04分23秒

realpath doesn't exist on Mac OS

2018年06月18日04分23秒