How do I get the path of the directory in which a Bash script is located, inside that script?
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
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
The chosen answer works very well. I'm posting my solution for anyone looking for shorter alternatives that still addresses sourcing, executing, full paths, relative paths, and symlinks. Finally, this will work on macOS, given that it cannot be assumed that GNU's coreutils' version of readlink is available.
The gotcha is that it's not using Bash, but it is easy to use in a Bash script. While the OP did not place any constraints on the language of the solution, it's probably best that most have stayed within the Bash world. This is just an alternative, and possibly an unpopular one.
PHP is available on macOS by default, and installed on a number of other platforms, though not necessarily by default. I realize this is a shortcoming, but I'll leave this here for any people coming from search engines, anyway.
export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
Here's an excerpt from my answer to shell script: check directory name and convert to lowercase in which I demonstrate not only how to solve this problem with very basic POSIX-specified utilities, I also address how to very simply store the function's results in a returned variable...
...Well, as you can see, with some help, I hit upon a pretty simple and very powerful solution:
I can pass the function a sort of messenger variable and dereference any explicit use of the resulting function's argument's $1
name with eval
as necessary, and, upon the function routine's completion, I use eval
and a backslashed quoting trick to assign my messenger variable the value I desire without ever having to know its name.
In full disclosure, ... (I found the messenger variable portion of this) and at Rich's sh tricks and I have also excerpted the relevant portion of his page below my own answer's excerpt.
... EXCERPT: ...
Though not strictly POSIX yet, realpath is a GNU core application since 2012. Full disclosure: never heard of it before I noticed it in the info coreutils
TOC and immediately thought of [the linked] question, but using the following function as demonstrated should reliably, (soon POSIXLY?), and, I hope, efficiently
provide its caller with an absolutely sourced $0
:
% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";
> }
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh
It may be worth highlighting that this solution uses POSIX parameter expansion to first check if the path actually needs expanding and resolving at all before attempting to do so. This should return an absolutely sourced $0
via a messenger variable (with the notable exception that it will preserve symlinks
) as efficiently as I could imagine it could be done whether or not the path is already absolute.
...
(minor edit: before finding realpath
in the docs, I had at least pared down my version of (the version below) not to depend on the time field (as it does in the first ps
command), but, fair warning, after testing some I'm less convinced ps
is fully reliable in its command path expansion capacity)
On the other hand, you could do this:
ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"
eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"
... And from Rich's sh tricks: ...
Returning strings from a shell function
As can be seen from the above pitfall of command substitution, standard output is not a good avenue for shell functions to return strings to their caller, unless the output is in a format where trailing newlines are insignificant. Certainly such practice is not acceptable for functions meant to deal with arbitrary strings. So, what can be done?
Try this:
func () {
body here
eval "$1=\${foo}"
}
Of course, ${foo}
could be replaced by any sort of substitution. The key trick here is the eval line and the use of escaping. The “$1”
is expanded when the argument to eval is constructed by the main command parser. But the “${foo}”
is not expanded at this stage, because the “$”
has been quoted. Instead, it’s expanded when eval evaluates its argument. If it’s not clear why this is important, consider how the following would be bad:
foo='hello ; rm -rf /'
dest=bar
eval "$dest=$foo"
But of course the following version is perfectly safe:
foo='hello ; rm -rf /'
dest=bar
eval "$dest=\$foo"
Note that in the original example, “$1”
was used to allow the caller to pass the destination variable name as an argument the function. If your function needs to use the shift command, for instance to handle the remaining arguments as “$@”
, then it may be useful to save the value of “$1”
in a temporary variable at the beginning of the function.
Here's a command that works under either Bash or zsh, and whether executed stand-alone or sourced:
[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
|| this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")
${(%):-%x}
${(%):-%x}
in zsh expands to the path of the currently-executing file.
:-
You know already that ${...}
substitutes variables inside of strings. You might not know that certain operations are possible (in both Bash and zsh) on the variables during substitution, like the fallback expansion operator :-
:
% x=ok
% echo "${x}"
ok
% echo "${x:-fallback}"
ok
% x=
% echo "${x:-fallback}"
fallback
% y=yvalue
% echo "${x:-$y}"
yvalue
%x
prompt escape codeNext, we'll introduce prompt escape codes, a zsh-only feature. In zsh, %x
will expand to the path of the file, but normally this is only when doing expansion for prompt strings. To enable those codes in our substitution, we can add a (%)
flag before the variable name:
% cat apath/test.sh
fpath=%x
echo "${(%)fpath}"
% source apath/test.sh
apath/test.sh
% cd apath
% source test.sh
test.sh
What we have so far works, but it would be tidier to avoid creating the extra fpath
variable. Instead of putting %x
in fpath
, we can use :-
and put %x
in the fallback string:
% cat test.sh
echo "${(%):-%x}"
% source test.sh
test.sh
Note that we normally would put a variable name between (%)
and :-
, but we left it blank. The variable with a blank name can't be declared or set, so the fallback is always triggered.
print -P %x
?Now we almost have the directory of our script. We could have used print -P %x
to get the same file path with fewer hacks, but in our case, where we need to pass it as an argument to dirname
, that would have required the overhead of a starting a new subshell:
% cat apath/test.sh
dirname "$(print -P %x)" # $(...) runs a command in a new process
dirname "${(%):-%x}"
% source apath/test.sh
apath
apath
It turns out that the hacky way is both more performant and succinct.
I would use something like this:
# Retrieve the full pathname of the called script
scriptPath=$(which $0)
# Check whether the path is a link or not
if [ -L $scriptPath ]; then
# It is a link then retrieve the target path and get the directory name
sourceDir=$(dirname $(readlink -f $scriptPath))
else
# Otherwise just get the directory name of the script path
sourceDir=$(dirname $scriptPath)
fi
Here is an easy-to-remember script:
DIR="$(dirname "${BASH_SOURCE[0]}")" # Get the directory name
DIR="$(realpath "${DIR}")" # Resolve its full path if need be
Try using:
real=$(realpath $(dirname $0))
I've compared many of the answers given, and came up with some more compact solutions. These seem to handle all of the crazy edge cases that arise from your favorite combination of:
script
, bash script
, bash -c script
, source script
, or . script
If you're running from Linux, it seems that using the proc
handle is the best solution to locate the fully resolved source of the currently running script (in an interactive session, the link points to the respective /dev/pts/X
):
resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"
This has a small bit of ugliness to it, but the fix is compact and easy to understand. We aren't using bash primitives only, but I'm okay with that because readlink
simplifies the task considerably. The echo X
adds an X
to the end of the variable string so that any trailing whitespace in the filename doesn't get eaten, and the parameter substitution ${VAR%X}
at the end of the line gets rid of the X
. Because readlink
adds a newline of its own (which would normally be eaten in the command substitution if not for our previous trickery), we have to get rid of that, too. This is most easily accomplished using the $''
quoting scheme, which lets us use escape sequences such as \n
to represent newlines (this is also how you can easily make deviously named directories and files).
The above should cover your needs for locating the currently running script on Linux, but if you don't have the proc
filesystem at your disposal, or if you're trying to locate the fully resolved path of some other file, then maybe you'll find the below code helpful. It's only a slight modification from the above one-liner. If you're playing around with strange directory/filenames, checking the output with both ls
and readlink
is informative, as ls
will output "simplified" paths, substituting ?
for things like newlines.
absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}
ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"
This one-liner works on Cygwin even if the script has been called from Windows with bash -c <script>
:
set mydir="$(cygpath "$(dirname "$0")")"
I want to make sure that the script is running in its directory. So
cd $(dirname $(which $0) )
After this, if you really want to know where the you are running then run the command below.
DIR=$(/usr/bin/pwd)
If your Bash script is a symlink, then this is the way to do it:
#!/usr/bin/env bash
dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
my_path="$dirn/$exec_dir";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"
X is the directory that contains your Bash script (the original file, not the symlink). I swear to God this works, and it is the only way I know of doing this properly.
The shortest and most elegant way to do this is:
#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY
This would work on all platforms and is super clean.
More details can be found in "Which directory is that bash script in?".
You can use $BASH_SOURCE
:
#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`
Note that you need to use #!/bin/bash
and not #!/bin/sh
since it's a Bash extension.
You can do that just combining the script name ($0
) with realpath
and/or dirname
. It works for Bash and Shell.
#!/usr/bin/env bash
RELATIVE_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"
echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"
The output will be something like this:
# RELATIVE_PATH->./bin/startup.sh<-
# RELATIVE_DIR_PATH->./bin<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/startup.sh<-
$0 is the name of the script itself
An example: LozanoMatheus/get_script_paths.sh
Look at the test at bottom with weird directory names.
To change the working directory to the one where the Bash script is located, you should try this simple, tested and verified with shellcheck solution:
#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2
The test:
$ ls
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko 46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS
Here is a POSIX compliant one-liner:
SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH
Keep it simple.
#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir
This gets the current working directory on Mac OS X v10.6.6 (Snow Leopard):
DIR=$(cd "$(dirname "$0")"; pwd)
For many cases, all you need to acquire is the full path to the script you just called. This can be easily accomplished using realpath
. Note that realpath
is part of GNU coreutils. If you don't have it already installed (it comes default on Ubuntu), you can install it with sudo apt update && sudo apt install coreutils
.
get_script_path.sh:
#!/bin/bash
FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# You can then also get the full path to the directory, and the base
# filename, like this:
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"
# Now print it all out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME = \"$SCRIPT_FILENAME\""
Example output:
~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh" SCRIPT_DIRECTORY = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash" SCRIPT_FILENAME = "get_script_path.sh"
Note that realpath
also successfully walks down symbolic links to determine and point to their targets rather than pointing to the symbolic link.
The code above is now part of my eRCaGuy_hello_world repo in this file here: bash/get_script_path.sh.
I usually use:
dirname $(which $BASH_SOURCE)
Based on this answer, I suggest the clarified version that gets SCRIPT_HOME
as the containing folder of any currently-running Bash script:
s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME
There is no 100% portable and reliable way to request a path to a current script directory. Especially between different backends like Cygwin, MinGW, MSYS, Linux, etc. This issue was not properly and completely resolved in Bash for ages.
For example, this could not be resolved if you want to request the path after the source
command to make nested inclusion of another Bash script which is in turn use the same source
command to include another Bash script and so on.
In case of the source
command, I suggest to replace the source
command with something like this:
function include()
{
if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
local include_file_path=...
else
local dir_path=... request the directory from the "$1" argument using one of answered here methods...
local include_file_path=...
fi
... push $CURRENT_SCRIPT_DIR in to stack ...
export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
source "$include_file_path"
... pop $CURRENT_SCRIPT_DIR from stack ...
}
From now on, the use of include(...)
is based on previous CURRENT_SCRIPT_DIR
in your script.
This only works when you can replace all source
commands by include
command. If you can't, then you have no choice. At least until developers of the Bash interpreter make an explicit command to request the current running script directory path.
My own closest implementation to this: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_entry
(search for the tkl_include
function)
pwd
can be used to find the current working directory, and dirname
to find the directory of a particular file (command that was run, is $0
, so dirname $0
should give you the directory of the current script).
However, dirname
gives precisely the directory portion of the filename, which more likely than not is going to be relative to the current working directory. If your script needs to change directory for some reason, then the output from dirname
becomes meaningless.
I suggest the following:
#!/bin/bash
reldir=`dirname $0`
cd $reldir
directory=`pwd`
echo "Directory is $directory"
This way, you get an absolute, rather than a relative directory.
Since the script will be run in a separate Bash instance, there isn't any need to restore the working directory afterwards, but if you do want to change back in your script for some reason, you can easily assign the value of pwd
to a variable before you change directory, for future use.
Although just
cd `dirname $0`
solves the specific scenario in the question, I find having the absolute path to more more useful generally.
Use dirname "$0"
:
#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"
Using pwd
alone will not work if you are not running the script from the directory it is contained in.
[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
I tried the followings with 3 different executions.
echo $(realpath $_)
. application # /correct/path/to/dir or /path/to/temporary_dir
bash application # /path/to/bash
/PATH/TO/application # /correct/path/to/dir
echo $(realpath $(dirname $0))
. application # failed with `realpath: missing operand`
bash application # /correct/path/to/dir
/PATH/TO/application # /correct/path/to/dir
echo $(realpath $BASH_SOURCE)
$BASH_SOURCE
is basically the same with ${BASH_SOURCE[0]}
.
. application # /correct/path/to/dir
bash application # /correct/path/to/dir
/PATH/TO/application # /correct/path/to/dir
Only $(realpath $BASH_SOURCE)
seems to be reliable.
One advantage of this method is that it doesn't involve anything outside Bash itself and does not fork any subshell neither.
First, use pattern substitution to replace anything not starting with /
(i.e., a relative path) with $PWD/
. Since we use a substitution to match the first character of $0
, we also have to append it back (${0:0:1}
in the substitution).
Now we have a full path to the script; we can get the directory by removing the last /
and anything the follows (i.e., the script name). That directory can then be used in cd
or as a prefix to other paths relative to your script.
#!/bin/bash
BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}
cd "$DIR"
If your script may be sourced rather than executed, you can of course replace $0
with ${BASH_SOURCE[0]}
, such as:
BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}
This will work for executable scripts too. It's longer, but more polyvalent.
$0
is not a reliable way to get the current script path. For example, this is my .xprofile
:
#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &
cd /tmp && ~/.xprofile && source ~/.xprofile
/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile
So please use BASH_SOURCE
instead.
The top response does not work in all cases...
As I had problems with the BASH_SOURCE with the included 'cd' approach on some very fresh and also on less fresh installed Ubuntu 16.04 (Xenial Xerus) systems when invoking the shell script by means of "sh my_script.sh", I tried out something different that as of now seems to run quite smoothly for my purposes. The approach is a bit more compact in the script and is further much lesser cryptic feeling.
This alternate approach uses the external applications 'realpath' and 'dirname' from the coreutils package. (Okay, not anyone likes the overhead of invoking secondary processes - but when seeing the multi-line scripting for resolving the true object it won't be that bad either having it solve in a single binary usage.)
So let’s see one example of those alternate solution for the described task of querying the true absolute path to a certain file:
SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
Or when having the chance of using paths with spaces (or maybe other special characters):
SCRIPT=`realpath -s "$0"`
SCRIPTPATH=`dirname "$SCRIPT"`
Indeed, if you don’t need the value of the SCRIPT variable then you might be able to merge this two-liner into even a single line. But why really shall you spend the effort for this?
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`;
SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
It works for all versions, including
source
" aka .
(dot) operator.$0
is modified from caller."./script"
"/full/path/to/script"
"/some/path/../../another/path/script"
"./some/folder/script"
Alternatively, if the Bash script itself is a relative symlink you want to follow it and return the full path of the linked-to script:
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
SCRIPT_PATH
is given in full path, no matter how it is called.
Just make sure you locate this at start of the script.
I don't think this is as easy as others have made it out to be. pwd
doesn't work, as the current directory is not necessarily the directory with the script. $0
doesn't always have the information either. Consider the following three ways to invoke a script:
./script
/usr/bin/script
script
In the first and third ways $0
doesn't have the full path information. In the second and third, pwd
does not work. The only way to get the directory in the third way would be to run through the path and find the file with the correct match. Basically the code would have to redo what the OS does.
One way to do what you are asking would be to just hardcode the data in the /usr/share
directory, and reference it by its full path. Data shoudn't be in the /usr/bin
directory anyway, so this is probably the thing to do.
Here is the simple, correct way:
actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")
Explanation:
${BASH_SOURCE[0]}
- the full path to the script. The value of this will be correct even when the script is being sourced, e.g. source <(echo 'echo $0')
prints bash, while replacing it with ${BASH_SOURCE[0]}
will print the full path of the script. (Of course, this assumes you're OK taking a dependency on Bash.)
readlink -f
- Recursively resolves any symlinks in the specified path. This is a GNU extension, and not available on (for example) BSD systems. If you're running a Mac, you can use Homebrew to install GNU coreutils
and supplant this with greadlink -f
.
And of course dirname
gets the parent directory of the path.
This should do it:
DIR="$(dirname "$(readlink -f "$0")")"
This works with symlinks and spaces in path.
See the man pages for dirname
and readlink
.
From the comment track it seems not to work with Mac OS. I have no idea why that is. Any suggestions?
Python was mentioned a few times. Here is the JavaScript (i.e., Node.js) alternative:
baseDirRelative=$(dirname "$0")
baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # Get absolute path using Node.js
echo $baseDir
This works in Bash 3.2:
path="$( dirname "$( which "$0" )" )"
If you have a ~/bin
directory in your $PATH
, you have A
inside this directory. It sources the script ~/bin/lib/B
. You know where the included script is relative to the original one, in the lib
subdirectory, but not where it is relative to the user's current directory.
This is solved by the following (inside A
):
source "$( dirname "$( which "$0" )" )/lib/B"
It doesn't matter where the user is or how he/she calls the script. This will always work.
This is what I crafted throughout the years to use as a header on my Bash scripts:
## BASE BRAIN - Get where you're from and who you are.
MYPID=$$
ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..
fa="$0" # First Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
[ "${fa:0:1}" = "/" ] && wa=$0 && break
[ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && break
ta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && break
done
SW="$wa"
SWDIR="$(dirname "$wa")"
SWBIN="$(basename "$wa")"
unset ta fa wa
( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1
At this point, your variables SW, SWDIR, and SWBIN contain what you need.
/tmp/a/b/c $ . ./test.sh
/tmp/a/b/c
/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c
/tmp/a/b/c $ ./test.sh
/tmp/a/b/c
/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c
/tmp/a/b/c $ cd
~ $ . /tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ . ../../tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ /tmp/a/b/c/test.sh
/tmp/a/b/c
~ $ ../../tmp/a/b/c/test.sh
/tmp/a/b/c
#!/usr/bin/env bash
# snagged from: https://stackoverflow.com/a/51264222/26510
function toAbsPath {
local target
target="$1"
if [ "$target" == "." ]; then
echo "$(pwd)"
elif [ "$target" == ".." ]; then
echo "$(dirname "$(pwd)")"
else
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
fi
}
function getScriptDir(){
local SOURCED
local RESULT
(return 0 2>/dev/null) && SOURCED=1 || SOURCED=0
if [ "$SOURCED" == "1" ]
then
RESULT=$(dirname "$1")
else
RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
fi
toAbsPath "$RESULT"
}
SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"
This is the only way I've found to tell reliably:
SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
This is how I work it on my scripts:
pathvar="$( cd "$( dirname $0 )" && pwd )"
This will tell you which directory the Launcher (current script) is being executed from.
The below stores the script's directory path in the dir
variable.
(It also tries to support being executed under Cygwin in Windows.)
And at last it runs the my-sample-app
executable with all arguments passed to this script using "$@"
:
#!/usr/bin/env sh
dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)
if [ -d /proc/cygdrive ]; then
case "$(uname -s)" in
CYGWIN*|MINGW32*|MSYS*|MINGW*)
# We are under Windows, so translate path to Windows format.
dir=$(cygpath -m "$dir");
;;
esac
fi
# Runs the executable which is beside this script
"${dir}/my-sample-app" "$@"
The key part is that I am reducing the scope of the problem: I forbid indirect execution of the script via the path (as in /bin/sh [script path relative to path component]
).
This can be detected because $0
will be a relative path which does not resolve to any file relative to the current folder. I believe that direct execution using the #!
mechanism always results in an absolute $0
, including when the script is found on the path.
I also require that the pathname and any pathnames along a chain of symbolic links only contain a reasonable subset of characters, notably not \n
, >
, *
or ?
. This is required for the parsing logic.
There are a few more implicit expectations which I will not go into (look at this answer), and I do not attempt to handle deliberate sabotage of $0
(so consider any security implications). I expect this to work on almost any Unix-like system with a Bourne-like /bin/sh
.
#!/bin/sh
(
path="${0}"
while test -n "${path}"; do
# Make sure we have at least one slash and no leading dash.
expr "${path}" : / > /dev/null || path="./${path}"
# Filter out bad characters in the path name.
expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
# Catch embedded new-lines and non-existing (or path-relative) files.
# $0 should always be absolute when scripts are invoked through "#!".
test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
# Change to the folder containing the file to resolve relative links.
folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
cd "${folder}"
# If the last path was not a link then we are in the target folder.
test -n "${path}" || pwd
done
)
$_
is worth mentioning as an alternative to $0
. If you're running a script from Bash, the accepted answer can be shortened to:
DIR="$( dirname "$_" )"
Note that this has to be the first statement in your script.
The dirname
command is the most basic, simply parsing the path up to the filename off of the $0
(script name) variable:
dirname "$0"
But, as matt b pointed out, the path returned is different depending on how the script is called. pwd
doesn't do the job because that only tells you what the current directory is, not what directory the script resides in. Additionally, if a symbolic link to a script is executed, you're going to get a (probably relative) path to where the link resides, not the actual script.
Some others have mentioned the readlink
command, but at its simplest, you can use:
dirname "$(readlink -f "$0")"
readlink
will resolve the script path to an absolute path from the root of the filesystem. So, any paths containing single or double dots, tildes and/or symbolic links will be resolved to a full path.
Here's a script demonstrating each of these, whatdir.sh
:
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"
Running this script in my home dir, using a relative path:
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat
Again, but using the full path to the script:
>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
Now changing directories:
>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
And finally using a symbolic link to execute the script:
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
$(dirname "$(readlink -f "$BASH_SOURCE")")
I think the simplest answer is a parameter expansion of the original variable:
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "opt1; original answer: $DIR"
echo ''
echo "opt2; simple answer : ${BASH_SOURCE[0]%/*}"
It should produce output like:
$ /var/tmp/test.sh
opt1; original answer: /var/tmp
opt2; simple answer : /var/tmp
The variable/parameter expansion ${BASH_SOURCE[0]%/*}"
seems much easier to maintain.
Source: Stackoverflow.com