evan_b
evan_b

Reputation: 1239

Bash function - return parent script file path

I have a bash script containing a function which is sourced by a number of different bash scripts. This function may fail based on its input, and I'd like to create logging within the function to identify what script(s) are causing failures.

E.g., source /path/to/function.sh

The closest I've come is this:

ps --no-heading -ocmd -p $$

This works well enough if the full file path is used to run the parent script, returning:

/bin/bash /path/to/parent.sh

But it fails to provide the full path if the parent script is run from a relative path, returning:

/bin/bash ./parent.sh

Ideally, I'd like a way to reliably return the parent script file path for both cases.

I suppose I could have each parent script pass its file path to the function (via $0 or similar), but that seems hard to enforce and not terribly elegant.

Any ideas, or alternative approaches? Should I not worry about the relative path case, and just use full/absolute file paths for everything?

Thanks!

I'm using Centos 5.9. Bash version - GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)

Upvotes: 1

Views: 1395

Answers (3)

Jan Matejka
Jan Matejka

Reputation: 1970

As soon as the parent script starts export

 "`pwd`/$0"

or so, into an env variable, say ORIG_SCRIPT, then in the function just use ORIG_SCRIPT.

You need to do this as soon as the script starts because $0 may be relative to the PWD and if you later change PWD before you need the value of ORIG_SCRIPT, it gets unnecessarily complicated.

Update:

Since you know the pid by $$, you may get something from /proc/<PID>/cmdline but I don't know how exactly this one works right now.

Upvotes: 1

konsolebox
konsolebox

Reputation: 75498

You could use ${BASH_SOURCE[1]} to get the script that calls the function but that is not always on absolute path form. You could get the absolute path of it by readlink -m, realpath, or other shell-script based solutions, but if your script changes directory from time to time, conversion of relative paths to absolute paths would no longer be accurate as those tools base from the current directory to get the actual form.

There's a workaround however but this requires that you won't change directories in your scripts before calling (sourcing) the script that contains the function. You would have to save the current directory in that script itself then base forming of absolute paths through that directory. You are free to change directories after the script has already been included. As an example:

ORIGINAL_PWD=$PWD

function x {
    local CALLING_SCRIPT="${BASH_SOURCE[1]}"

    if [[ -n $CALLING_SCRIPT ]]; then
        if [[ $CALLING_SCRIPT == /* ]]; then
            CALLING_SCRIPT=$(readlink -m "$CALLING_SCRIPT")
        else
            CALLING_SCRIPT=$(readlink -m "$ORIGINAL_PWD/$CALLING_SCRIPT")
        fi

        echo "Calling script: $CALLING_SCRIPT"
    else
        echo "Caller is not a script."
    fi
}

Or

ORIGINAL_PWD=$PWD

function getabspath {
    local -a T1 T2
    local -i I=0
    local IFS=/ A

    case "$1" in
    /*)
        read -r -a T1 <<< "$1"
        ;;
    *)
        read -r -a T1 <<< "/$PWD/$1"
        ;;
    esac

    T2=()

    for A in "${T1[@]}"; do
        case "$A" in
        ..)
            [[ I -ne 0 ]] && unset T2\[--I\]
            continue
            ;;
        .|'')
            continue
            ;;
        esac

        T2[I++]=$A
    done

    case "$1" in
    */)
        [[ I -ne 0 ]] && __="/${T2[*]}/" || __=/
        ;;
    *)
        [[ I -ne 0 ]] && __="/${T2[*]}" || __=/.
        ;;
    esac
}

function x {
    local CALLING_SCRIPT="${BASH_SOURCE[1]}"

    if [[ -n $CALLING_SCRIPT ]]; then
        if [[ $CALLING_SCRIPT == /* ]]; then
            getabspath "$CALLING_SCRIPT"
        else
            getabspath "$ORIGINAL_PWD/$CALLING_SCRIPT"
        fi

        echo "Calling script: $__"
    else
        echo "Caller is not a script."
    fi
}

You could also play around with FUNCNAME and BASH_LINENO to be more specific with the errors. I'm just not sure if they're already supported in Bash 3.2.

If you actually had Bash 4.0+ you could make use of associative arrays to map absolute paths with it but if there are two scripts with the same names or are called with almost similar names, one value could be overridden. There's no fix to that since we can't choose our keys from BASH_SOURCE.

Added Note: You could also prevent your script from being unnecessarily sourced multiple times as it only requires to be once through a solution like Shell Script Loader. You might find convenience through it as well.

Upvotes: 0

seanmcl
seanmcl

Reputation: 9946

You can use readlink to follow all symbolic links to get an absolute path.

echo $(readlink -f $0)

Upvotes: 1

Related Questions