Unix shell script find out which directory the script file resides?
Basically I need to run the script with paths related to the shell script file location, how can I change the current directory to the same directory as where the script file resides?
Is that really a duplicate? This question is about a «unix shell script», the other specifically about Bash.
@BoltClock: This question was improperly closed. The linked question is about Bash. This question is about Unix shell programming. Notice that the accepted answers are quite different!
Google brought me here as I was looking for a pure posix implementation of the solution. After a little bit more searching I came across this very detailed answer explaining why it can’t be done and a way to work around it by shimming support for popular shells. stackoverflow.com/a/29835459/816584
19 Answers 19
In Bash, you should get what you need like this:
#!/usr/bin/env bash BASEDIR=$(dirname "$0") echo "$BASEDIR"
This doesn’t work if you’ve called the script via a symbolic link in a different directory. To make that work you need to use readlink as well (see al’s answer below)
In bash it is safer to use $BASH_SOURCE in lieu of $0 , because $0 doesn’t always contain the path of the script being invoked, such as when ‘sourcing’ a script.
@auraham: CUR_PATH=$(pwd) or pwd do return the current directory (which does not have to be the scripts parent dir)!
I tried the method @mklement0 recommended, using $BASH_SOURCE , and it returns what I needed. My script is being called from another script, and $0 returns . while $BASH_SOURCE returns the right subdirectory (in my case scripts ).
The original post contains the solution (ignore the responses, they don’t add anything useful). The interesting work is done by the mentioned unix command readlink with option -f . Works when the script is called by an absolute as well as by a relative path.
#!/bin/bash # Absolute path to this script, e.g. /home/user/bin/foo.sh SCRIPT=$(readlink -f "$0") # Absolute path this script is in, thus /home/user/bin SCRIPTPATH=$(dirname "$SCRIPT") echo $SCRIPTPATH
#!/bin/tcsh # Absolute path to this script, e.g. /home/user/bin/foo.csh set SCRIPT=`readlink -f "$0"` # Absolute path this script is in, thus /home/user/bin set SCRIPTPATH=`dirname "$SCRIPT"` echo $SCRIPTPATH
Note: Not all systems have readlink . That’s why I recommended using pushd/popd (built-ins for bash).
The -f option to readlink does something different on OS X (Lion) and possibly BSD. stackoverflow.com/questions/1055671/…
To clarify @Ergwun’s comment: -f is not supported on OS X at all (as of Lion); there you can either drop the -f to make do with resolving at most one level of indirection, e.g. pushd «$(dirname «$(readlink «$BASH_SOURCE» || echo «$BASH_SOURCE»)»)» , or you can roll your own recursive symlink-following script as demonstrated in the linked post.
I still don’t understand, why the OP would need the absolute path. Reporting «.» should work alright if you want to access files relative to the scripts path and you called the script like ./myscript.sh
@StefanHaberl I think it would be an issue if you ran the script while your present working directory was different from the script’s location (e.g. sh /some/other/directory/script.sh) , in this case . would be your pwd, not /some/other/directory
An earlier comment on an answer said it, but it is easy to miss among all the other answers.
echo this file: "$BASH_SOURCE" echo this dir: "$(dirname "$BASH_SOURCE")"
A more explicit way to print the directory would, according to tool ShellCheck, be: «$(dirname «$
Assuming you’re using bash
#!/bin/bash current_dir=$(pwd) script_dir=$(dirname "$0") echo $current_dir echo $script_dir
This script should print the directory that you’re in, and then the directory the script is in. For example, when calling it from / with the script in /home/mez/ , it outputs
Remember, when assigning variables from the output of a command, wrap the command in $( and ) — or you won’t get the desired output.
To me, $current_dir is indeed the path I’m calling the script from. However, $script_dir is not the script’s dir, it’s just a dot.
@Michael The script_dir is relative to current_dir. So if you run the script from the directory it is stored in, you’ll simply get a dot in script_dir.
$(pwd) is a lot of overhead compared to $PWD , which POSIX explicitly requires to be set on shell startup and on cd .
One-liner which will give you the full directory name of the script no matter where it is being called from.
To understand how it works you can execute the following script:
#!/bin/bash SOURCE="$" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink TARGET="$(readlink "$SOURCE")" if [[ $TARGET == /* ]]; then echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'" SOURCE="$TARGET" else DIR="$( dirname "$SOURCE" )" echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')" SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located fi done echo "SOURCE is '$SOURCE'" RDIR="$( dirname "$SOURCE" )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" if [ "$DIR" != "$RDIR" ]; then echo "DIR '$RDIR' resolves to '$DIR'" fi echo "DIR is '$DIR'"
#!/bin/bash pushd $(dirname "$") > /dev/null basedir=$(pwd -L) # Use "pwd -P" for the path without links. man bash for more info. popd > /dev/null echo "$"
You can replace the pushd / popd with cd $(dirname «$<0>«) and cd — to make it work on other shells, if they have a pwd -L .0>
So I don’t have to store the original directory in a variable. It’s a pattern I use a lot in functions and such. It nests really well, which is good.
It is still being stored in memory — in a variable — whether a variable is referenced in your script or not. Also, I believe the cost of executing pushd and popd far outweighs the savings of not creating a local Bash variable in your script, both in CPU cycles and readability.
If you want to get the actual script directory (irrespective of whether you are invoking the script using a symlink or directly), try:
BASEDIR=$(dirname $(realpath "$0")) echo "$BASEDIR"
This works on both linux and macOS. I couldn’t see anyone here mention about realpath . Not sure whether there are any drawbacks in this approach.
on macOS, you need to install coreutils to use realpath . Eg: brew install coreutils .
BASEDIR=$(dirname $0) echo $BASEDIR
This works unless you execute the script from the same directory where the script resides, in which case you get a value of ‘.’
To get around that issue use:
current_dir=$(pwd) script_dir=$(dirname $0) if [ $script_dir = '.' ] then script_dir="$current_dir" fi
You can now use the variable current_dir throughout your script to refer to the script directory. However this may still have the symlink issue.
Let’s make it a POSIX oneliner:
Tested on many Bourne-compatible shells including the BSD ones.
As far as I know I am the author and I put it into public domain. For more info see: https://www.bublina.eu.org/posts/2017-05-11-posix_shell_dirname_replacement/
as written, cd: too many arguments if spaces in the path, and returns $PWD . (an obvious fix, but just shows how many edge cases there actually are)
Would upvote. Except for the comment from @michael that it fails with spaces in path. Is there a fix for that?
ln -s /home/der/1/test /home/der/2/test && /home/der/2/test => /home/der/2 (should show path to the original script instead)
BASE_DIR="$(cd "$(dirname "$0")"; pwd)"; echo "BASE_DIR => $BASE_DIR"
INTRODUCTION
This answer corrects the very broken but shockingly top voted answer of this thread (written by TheMarko):
#!/usr/bin/env bash BASEDIR=$(dirname "$0") echo "$BASEDIR"
WHY DOES USING dirname «$0» ON IT’S OWN NOT WORK?
dirname $0 will only work if user launches script in a very specific way. I was able to find several situations where this answer fails and crashes the script.
First of all, let’s understand how this answer works. He’s getting the script directory by doing
$0 represents the first part of the command calling the script (it’s basically the inputted command without the arguments:
/some/path/./script argument1 argument2
dirname basically finds the last / in a string and truncates it there. So if you do:
This example works well because /usr/bin/sha256sum is a properly formatted path but
wouldn’t work well and would give you:
BASENAME="/some/path/." #which would crash your script if you try to use it as a path
Say you’re in the same dir as your script and you launch it with this command
$0 in this situation will be ./script and dirname $0 will give:
. #or BASEDIR=".", again this will crash your script
Without inputting the full path will also give a BASEDIR=».»
Using relative directories:
If you’re in the /some directory and you call the script in this manner (note the absence of / in the beginning, again a relative path):
You’ll get this value for dirname $0:
and ./path/./script (another form of the relative path) gives:
The only two situations where basedir $0 will work is if the user use sh or touch to launch a script because both will result in $0:
which will give you a path you can use with dirname.
THE SOLUTION
You’d have account for and detect every one of the above mentioned situations and apply a fix for it if it arises:
#!/bin/bash #this script will only work in bash, make sure it's installed on your system. #set to false to not see all the echos debug=true if [ "$debug" = true ]; then echo "\$0=$0";fi #The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script) #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script) #situation3: different dir but relative path used to launch script if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1 _B2=$-2))>; B_=$; B_2=$; B_3=$ # -1))>;fi #fix for situation2 # "; else BASEDIR="/$";fi else #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi fi fi if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
Команда which в Linux
В этом руководстве мы рассмотрим команду Linux which .
Linux, which команда используется для определения местоположения данного исполняемого файла, который выполняется при вводе имени исполняемого файла (команды) в строке терминала. Команда ищет исполняемый файл, указанный в качестве аргумента, в каталогах, перечисленных в переменной среды PATH.
Что такое PATH
В Linux PATH — это переменная окружения, которая сообщает оболочке и другим программам, в каких каталогах искать исполняемые файлы. Он состоит из списка разделенных двоеточиями абсолютных путей к каталогам, содержащим исполняемые файлы.
Чтобы просмотреть содержимое переменной PATH, используйте команду echo с $PATH в качестве аргумента:
Результат будет выглядеть примерно так:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Как использовать команду which
Синтаксис команды which следующий:
Например, чтобы найти полный путь к команде ping , вы должны ввести следующее:
Результат будет примерно таким:
Вы также можете указать несколько аргументов для команды which :
Вывод будет включать полные пути к исполняемым файлам netcat и uptime :
Поиск производится слева направо, а если более одного совпадения найдены в каталогах , перечисленных в PATH переменной пути, which будет печатать только первый. Чтобы распечатать все совпадения, используйте параметр -a :
В выводе будут показаны два полных пути к touch команде :
Обычно один из исполняемых файлов является лишь symlink на другой, но в некоторых случаях у вас могут быть две версии одной и той же команды, установленные в разных местах, или совершенно разные команды с одним и тем же именем.
Выводы
Команда which используется для поиска команды путем поиска исполняемого файла команды в каталогах, указанных переменной окружения PATH .
Если у вас есть вопросы или отзывы, оставьте комментарий ниже.