3

Let us say I have the following script files:

~/src/setup.sh::

#!/usr/bin/env bash

dn=$( dirname "$0" )
source "$dn/init/init.sh"

~/src/init/init.sh:

#!/usr/bin/env bash

dn=$( dirname "$0" )
source "$dn/start.sh"

start_servers "param1" "param2"

~/src/init/start.sh:

#!/usr/bin/env bash

start_servers() {
  # ...
  printf "start the servers..."
  # ...
}

Sourcing the second file (start.sh) results that:

$ ./setup.sh
./init/init.sh: line 4: ./start.sh: No such file or directory
./init/init.sh: line 6: start_servers: command not found

Since I execute the setup.sh from ., after sourcing the files, start.sh seems to be sourced from . as well but I would like to source it from its proper location.

Any idea how to fix it? Thanks in advance.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Mark
  • 5,994
  • 5
  • 42
  • 55
  • 1
    `$0` in the sourced script will be the parent's `$0`, which is why this fails. See [this answer](http://stackoverflow.com/a/246128/1899640) for how to determine the current script file's location – that other guy Dec 15 '16 at 21:55
  • `source` will search the directories in `$PATH`. Maybe you should just put the directory containing all these scripts in `$PATH`. – Barmar Dec 15 '16 at 22:21
  • 2
    There's another potential problem here: even after this is fixed, setup.sh sets `dn` to ~/src, and then init.sh sets it to ~/src/init; when init.sh finishes, it's *still* going to be set to ~/src/init, so if setup.sh uses it after sourcing init.sh, it's not going to be pointing at the expected location. – Gordon Davisson Dec 15 '16 at 22:31
  • @GordonDavisson: true, thank you for it! – Mark Dec 15 '16 at 22:35

2 Answers2

5

Bash has the built-in $BASH_SOURCE variable, which is similar to $0, but - unlike the latter - correctly reflects the name of the running script even when sourced.

Thus, simply replacing $0 with $BASH_SOURCE in your scripts should be enough.

mklement0
  • 382,024
  • 64
  • 607
  • 775
-2

If you use sh instead of source, it will load up the environment as you expect, instead of executing the script in the same environment:

dn=$( dirname "$0" )
sh "$dn/init/init.sh"

This change will cause the code to run in a subshell instead of the same shell. You won't be able to do this for a script that needs a function from an outer shell, since you will still need to source that to have access to the function. But in your case, the only script that needs that can still be sourced, since you don't need $0 there.

Jeremy Gurr
  • 1,613
  • 8
  • 11
  • 1
    `start_servers` won't be available to `init.sh` if `start.sh` is run in a sub-shell. – John Kugelman Dec 15 '16 at 22:17
  • start.sh doesn't use $0 anyway, so you can keep that as source, and start_servers will still work. – Jeremy Gurr Dec 15 '16 at 23:04
  • FYI you shouldn't downvote answers just because they aren't your favorite one. Upvote the ones you like best, and only downvote severely erroneous information. Even then, you should try and correct it with comments first, and only downvote if the poster refuses to correct the misinformation. Mine might not be the ultimate solution to the given problem, but it is a usable one. – Jeremy Gurr Dec 15 '16 at 23:05
  • I didn't down-vote, but @JohnKugelman has pointed out a fundamental flaw in your answer: If you replace all `source` calls with `sh` calls, which you seem to recommend ("do this change in each script"), function `start_servers()` won't be available in `init.sh`. Aside from that, you shouldn't invoke scripts that were written for `bash` with `sh`. Finally a nit-pick: Passing a script to `sh` doesn't run it in a _subshell_, but in _a child process that happens to be a shell_. – mklement0 Dec 16 '16 at 02:34
  • Thank you, that is much more helpful than a down vote. One question though, isn't the definition of subshell "a child process that happens to be a shell"? Or is there some meaningful concept in linux that subshell refers to that is distinct from this? – Jeremy Gurr Dec 16 '16 at 10:26
  • If you write `(cmd)`, `cmd` will execute in a sub-shell and will inherit the parent shell's variables and functions. If you do `sh -c 'cmd'`, it will only see the definitions that have been explicitly *exported*. – John Kugelman Dec 16 '16 at 13:50
  • John has thankfully already answered your question; let me add that you need to @-mention users for them to be notified of follow-up comments. As for down-votes: Down-voting without a comment is rarely helpful, but _with_ a comment it makes sense (as happened here; it's legitimate for other down-voters to not also comment if they agree with the first comment). It _may_ be the start of a dialog that improves the answer (which _may_ lead to undoing the down-vote or even converting it into an up-vote). – mklement0 Dec 16 '16 at 16:19