87

I have two Bash scripts in the same folder (saved somewhere by the user who downloads the entire repository):

  • script.sh is run by the user
  • helper.sh is required and run by script.sh

The two scripts should be in the same directory. I need the first script to call the second one, but there are two problems:

  1. Knowing the current working directory is useless to me, because I don't know how the user is executing the first script (could be with /usr/bin/script.sh, with ./script.sh, or it could be with ../Downloads/repo/scr/script.sh)
  2. The script script.sh will be changing to a different directory before calling helper.sh.

I can definitely hack together Bash that does this by storing the current directory in a variable, but that code seems needlessly complicated for what I imagine is a very common and simple task.

Is there a standard way to reliably call helper.sh from within script.sh? And will work in any Bash-supported operating system?

Community
  • 1
  • 1
IQAndreas
  • 8,060
  • 8
  • 39
  • 74

5 Answers5

84

Since $0 holds the full path of the script that is running, you can use dirname against it to get the path of the script:

#!/bin/bash

script_name=$0
script_full_path=$(dirname "$0")

echo "script_name: $script_name"
echo "full path: $script_full_path"

so if you for example store it in /tmp/a.sh then you will see an output like:

$ /tmp/a.sh
script_name: /tmp/a.sh
full path: /tmp

so

  1. Knowing the current working directory is useless to me, because I don't know how the user is executing the first script (could be with /usr/bin/script.sh, with ./script.sh, or it could be with ../Downloads/repo/scr/script.sh)

Using dirname "$0" will allow you to keep track of the original path.

  1. The script script.sh will be changing to a different directory before calling helper.sh.

Again, since you have the path in $0 you can cd back to it.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • 10
    Briefly, using `$0` won't work as expected with script sourcing. For bash there is the `BASH_SOURCE` alternative. – Alex Che Aug 21 '18 at 15:54
  • $0 will NOT work for sub script files executed inside the original script – Nam G VU Nov 29 '22 at 17:27
  • @AlexChe I used `${BASH_SOURCE%/*}` and it worked when executed manually but failed when started by cron. Changed it to `$(dirname "$0")` and now everything works fine. – AndreKR Apr 06 '23 at 02:52
  • @AndreKR, is it possible that cron uses other shell, not Bash, on your system? See this [question](https://serverfault.com/questions/678410/what-shell-is-used-by-cron) for an example. – Alex Che Apr 06 '23 at 11:14
  • 1
    @AlexChe Good point. I didn't have a shebang in the script and the crontab shell is set to `/bin/sh` - which apparently on Ubuntu is `dash`. – AndreKR Apr 06 '23 at 14:48
52

$0 is considered unsafe and fragile by many devs. I have found another solution, it is safe for a chain of bash scripts and source.

If a.sh needs to execute b.sh (located in the same folder) using a child bash process:

#!/bin/bash
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
bash ${__dir}/b.sh

If a.sh needs to execute b.sh (located in the same folder) using the same bash process:

#!/bin/bash
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source ${__dir}/b.sh
Eugen
  • 620
  • 5
  • 8
  • Your link points to a deleted comment. Do you have another source to support the unsafe claim? Related posts that explain about the issues with `$0`: [choosing between $0 and BASH_SOURCE](https://stackoverflow.com/q/35006457/7328782), [how to get script directory in POSIX sh?](https://stackoverflow.com/q/29832037/7328782). – Cris Luengo Jan 07 '20 at 18:29
  • @CrisLuengo Believe this is the link: http://mywiki.wooledge.org/BashFAQ/028 – John Drinane Feb 11 '21 at 19:23
  • @CrisLuengo actually the accepted answer at least explains why `$0` is the incorrect thing to do. Cause `$0` gives the wrong location in the case the script is sourced, cause then `$0` contains the location of the script that is doing the sourcing and not the location of the script being sourced. – Maarten Derickx Nov 26 '21 at 11:04
  • I used `${BASH_SOURCE%/*}` and it worked when executed manually but failed when started by cron. Changed it to `$(dirname "$0")` and now everything works fine. – AndreKR Apr 06 '23 at 02:48
13

Is there a standard way to reliably call helper.sh from within script.sh? And will work in any Bash-supported operating system?

In most cases, when helper.sh is in the same directory as script.sh, you can use in script.sh command:

. ${0%/*}/helper.sh

Explanation:
$0 stores the name of your process (in most cases it's the full path to your script).
${parameter%word} removes suffix pattern word from variable $parameter (in the command above it removes file name /* from the full path stored in variable $0).

If for some reasons (described in other answers) you don't want to use $0, you can use $BASH_SOURCE instead:

. ${BASH_SOURCE%/*}/helper.sh

And if you want, you can use source instead of .:

source ${BASH_SOURCE%/*}/helper.sh

As for me, it's the easiest way to achieve your goal.

Ivan Olshansky
  • 889
  • 2
  • 17
  • 23
  • I used `${BASH_SOURCE%/*}` and it worked when executed manually but failed when started by cron. Changed it to `$(dirname "$0")` and now everything works fine. – AndreKR Apr 06 '23 at 02:48
1

This is the standard variable block I have in all of my scripts:

# Initialize global variables.
{
  declare SCRIPT_INVOKED_NAME="${BASH_SOURCE[${#BASH_SOURCE[@]}-1]}"
  declare SCRIPT_NAME="${SCRIPT_INVOKED_NAME##*/}"
  declare SCRIPT_INVOKED_PATH="$( dirname "${SCRIPT_INVOKED_NAME}" )"
  declare SCRIPT_PATH="$( cd "${SCRIPT_INVOKED_PATH}"; pwd )"
  declare SCRIPT_RUN_DATE="$( date )"
}

I then use SCRIPT_PATH to load other scripts in the same directory:

source "${SCRIPT_PATH}/script-helpers.inc.sh"
Dejay Clayton
  • 3,710
  • 2
  • 29
  • 20
0

You can use the $0 variable. But this won't be as simple as getting current script name:

d=${0%/*}
[ x"$d" = x"$0" ] && d=.   # portable variant, use [[ ...]] in bash freely
readonly d                 # optional, for additional safety

# ... later on
. "$d"/helper.sh

This works well in set -e case as well.

  • What is the `[ x"$d" = x"$0" ]` syntax please? What is the syntactical function of `x` before the arguments to the equality operator. Or in the following idiom `[ -z ${var+x} ]` ? – von spotz Jun 09 '21 at 07:06
  • 1
    @vonspotz For the first question: it saves you from errors that may be caused if either `$0` or `$d` become starting with a hypen - otherwise it would be interpreted as a directive to `[ ... ]` command. – Vadim Zhukov Jun 09 '21 at 11:33
  • 1
    @vonspotz For the second question: `${var+x}` means "substitute with `x` if `$var` is set". Even if `$var` is empty, this would result in `x` string being substituted. But in case `$var` was not set at all, nothing will be substituted. So this is a totally different to the `[ x"$foo" = x"$bar" ]` question. – Vadim Zhukov Jun 09 '21 at 11:40