15

I have the following problem, my shell script contain something like:

mydir=''

# config load
source $mydir/config.sh

.... execute various commands

My script is placed in my user dir.. let's say /home/bob/script.sh

If I'm inside the /home/bob dir and run ./script.sh everything works fine.

If I'm outside and want to use the absolute path /home/bob/script.sh the config.sh file is not recalled properly.

What value should i assign to $mydir in order to make the script runnable from every path without struggle?

mydir=$(which command?)

PS: as bonus please also provide an alternative if the script dir is inside the $PATH

user3450548
  • 2,868

4 Answers4

16

The $0 variable contains the script's path:

$ cat ~/bin/foo.sh
#!/bin/sh
echo $0

$ ./bin/foo.sh
./bin/foo.sh

$ foo.sh
/home/terdon/bin/foo.sh

$ cd ~/bin
$ foo.sh
./foo.sh

As you can see, the output depends on the way it was called, but it always returns the path to the script relative to the way the script was executed. You can, therefore, do:

## Set mydir to the directory containing the script
## The ${var%pattern} format will remove the shortest match of
## pattern from the end of the string. Here, it will remove the
## script's name,. leaving only the directory. 
mydir="${0%/*}"

# config load
source "$mydir"/config.sh

If the directory is in your $PATH, things are even simpler. You can just run source config.sh. By default, source will look for files in directories in $PATH and will source the first one it finds:

$ help source
source: source filename [arguments]
    Execute commands from a file in the current shell.

Read and execute commands from FILENAME in the current shell.  The
entries in $PATH are used to find the directory containing FILENAME.
If any ARGUMENTS are supplied, they become the positional parameters
when FILENAME is executed.

If you are sure your config.sh is unique or, at least, that it is the first one found in $PATH, you can source it directly. However, I suggest you don't do this and stick to the first method instead. You never know when another config.sh might be in your $PATH.

terdon
  • 242,166
  • 1
    ~/bin/foo.sh it is just tilde expansion , not a full path. Use readlink -e $0 instead – Costas Mar 31 '16 at 09:15
  • @Costas I edited to clarify that $0 is not always the absolute path. It is always the path to the script though, so this approach should work. Can you think of cases that would break it? – terdon Mar 31 '16 at 09:23
  • This didn't work for me when the script is called via sh nameofscript.sh, the name of the script is matched so mydir='nameofscript.sh'. – ian Feb 28 '20 at 05:53
  • @iain no, of course it won't work then, but that's a different issue. The question here was about executing the script by name. If you pass the script without a path as an argument to sh, then it's a totally different situation since $0 will not contain the path. It will work as expected if you use sh ./nameofscript.sh or sh /path/to/nameofscript.sh. If you need it to work specifically for when you call it with just the name and nothing else, please ask a new question. – terdon Feb 28 '20 at 09:32
  • I didn't say the answer was wrong with regards to the question but that it didn't work given a different way of calling it, one which is normal and valid, but more importantly, it's a helpful piece of information to add to an answer via comment. "of course" is not something I'd consider a valid response on a question and answer site, its whole reason for being is ignorance, but thanks for your hard work on the site, I managed to get what I wanted from another answer. – ian Feb 28 '20 at 11:19
  • @iain I'm sorry, I didn't mean to be dismissive (but on re-reading my comment I do see how it could have come across that way; my bad, I shouldn't answer comments before coffee). My suggestion to ask a new question was honestly offered. That's the best way to get the information you need and we try to avoid having conversations in the comments. Also, since I personally never call a script with shellName scriptName but always use shebangs instead, I admit I consider this a bit of an edge case. But that's my hangup. – terdon Feb 28 '20 at 11:29
  • @terdon Thank you, that's a very gracious apology, and my own comment history shows I fully understand the need for helpful coffee at times! :) My own apologies for misreading what you clearly didn't intend. Have a good day. – ian Mar 02 '20 at 00:33
12

This method is only useful in a bash script.

Use:

mydir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"

How it works:

BASH_SOURCE is an array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined.

So, with:

cd "$( dirname "${BASH_SOURCE[0]}" )"

you move to the directory where the script is placed.

Then, the output of cd is sent to /dev/null, because sometimes, it prints something to STDOUT. E.g., if your $CDPATH has .

And finally, it executes:

pwd

which gets the current location.

Source:

https://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in?page=1&tab=oldest#tab-top

  • never hurts to explain a little bit about how your solution works. I know the Q is tagged bash, but the use of BASH_SOURCE does make this A bash-specific. There were other concerns brought up in the comment section of that SO answer as well that would be good to mention. – Jeff Schaller Mar 31 '16 at 11:52
9

Found the solution:

mydir=$(dirname "$0")

With this the script can be invoked from everywhere without throubles.

user3450548
  • 2,868
  • 2
    not if it is a symlink. This one is the most reliable I have found: current_dir=$(dirname $(readlink -f $0)) – Alexar Apr 07 '18 at 09:23
2

Give a try to this tested and verified with shellcheck solution:

mydir="$(dirname "${0}")"
source "${mydir}"/config.sh
printf "read value of config_var is %s\n" "${config_var}"

The test:

$ ls 
script.sh
config.sh
$ cat script.sh
#!/bin/bash --
mydir="$(dirname "${0}")"
source "${mydir}"/config.sh

printf "read value of config_var is %s\n" "${config_var}"

$ cat config.sh
config_var=super_value

$ 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 *.sh *testdir
$ *testdir/script.sh
read value of config_var is super_value
Jay jargot
  • 1,175