5

How can I get this script file's functions to load without having to source it every time?

I created a file foo with script functions I'd like to run. It's in /usr/bin, which is in the PATH.

File foo:

#!/bin/bash
echo "Running foo"

function x {
    echo "x"
}

However, when I type a function name like x in the terminal:
x: command not found

When I type foo:
Running foo shows up (so the file is in PATH and is executable)

After typing source foo, I can type x to run function x

A pretty basic question, I know. I'm just trying to abstract my scripts so they're more manageable (compared to dumping everything in .profile or .bashrc).

1
  • This solves the core problem of repeatedly sourcing files, but is unrelated to the bash error. I switched to zsh + oh-my-zsh, which automatically sources the scripts in ~/.oh-my-zsh/custom/ Here's the full setup I use now: github.com/mrgnw/mzsh
    – mrgnw
    May 10, 2016 at 20:01

2 Answers 2

8

Your problem is that the script in your .../bin directory is execed in another shell environment - its environment does not survive its execution and so the x() { ... ; } definition does not survive into the current shell environment when it completes.

When you . ./somescript.sh (or source in some shells such as bash and zsh) the current shell reads the script into the current environment and executes the contents as if issued from the prompt - so x() { ... ; } is defined in the current shell environment - your interactive shell.

Basically the problem can be demonstrated like this:

sh <<\HEREDOC_CMD_FILE #runs sh shell with heredoc input file
    { #begins current shell compound expression
        ( #begins subshell compound expression
            x() { echo 'I am function x.' ; } #define function x
            x #x invoked in subshell
        ) #ends subshell compound expression
        x #x invoked in current shell
    } #ends current shell compound expression
#v_end_v
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.
sh: line 6: x: command not found

In the same way the environment defined in the ( : subshell ) in the above example does not survive its completion, neither does that defined in your executed script. Similarly, when sh reads in the HEREDOC_CMD_FILE it is performing the same function as your source ../file and running its contents in its current shell execution environment - though its environment is a subshell child to the shell issuing the prompt at which it is run, and so none of its environment survives its completion from there, either. You can make it do so, though, like:

. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
    x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.

... which is pretty much exactly what you do with source ${script} except that here we .dot source the contents of stdin whereas you source the contents of a file on disk

The primary difference, though, between a ( : subshell ) and an executed script is what an executed script's execution environment looks like. When you execute a script it is provided a new environment in which to operate - so any variables declared in your current shell do not carry over to its environment unless explicitly exported or declared on its command line. This behavior can be demonstrated thus:

{   x() { printf "$FMT" "$0" "$var2" x ; } #describe env
    export FMT='argv0: %s\t\tvar2: %s\t\tI am function %s.\n' \
        var1=val1 #var1 and FMT are explicitly exported
    var2=shell_val2 #var2 is not
    cat >./script && #read out stdin to >./script
        chmod +x ./script && #then make ./script executable
        var3=val3 ./script #then define var3 for ./script's env and execute
} <<\SCRIPT ; x ; y #send heredoc to block's stdin then call {x,y}()
#!/usr/bin/sh
#read out by cat to >./script then executed in a separate environment
y() { printf "$FMT" "$0" "$var2" y ; } #describe env
echo "${var1:-#var1 is unset or null}" #$var1 if not unset or null else :-this} 
echo "${var2:-#var2 is unset or null}"
echo "${var3:-#var3 is unset or null}"
export var2=script_val2
x ; y #run x() ; y() in script
#v_end_v                                                                                                                                                                                          
SCRIPT

###OUTPUT###
val1
#var2 is unset or null
val3
./script: line 8: x: command not found
argv0: ./script         var2: script_val2               I am function y.
argv0: sh               var2: shell_val2                I am function x.
sh: line 18: y: command not found

This behavior differs from that of ( : subshells ) run at the prompt or otherwise as children of the current shell because they automatically inherit the environment of their parent, whereas - as demonstrated above - executed children require that it be explicitly exported.

var1=val1 ; export var2=val2
x() { echo 'I am function x.' ; }
( 
    printf '%s\n' "$var1" "$var2" 
    x
)

###OUTPUT###
val1
val2
I am function x.

The last thing I should note is that there is no portable way to export functions from a parent shell to an executed script without .dot sourcing a file containing the function definition within the executed script, as you do with source in your current shell. Basically, you cannot portably export function as you can with variables. Though, I suppose you might export fn_def='fn() { : fn body ; }' then eval "$fn_def" in your executed script. And, of course, any child environment - executed or otherwise - dies with the child.

So if you want the function defined in the script, but do not want to source the script itself, you have to read the function as output from some command and eval it.

eval "$(cat ./script)"

But this is practically the same thing as . ./script - just less efficient. You might as well just source it.

The best way to do this is to remove the function definition from the script itself and put it instead in its own file, then source it from both your script and from the current shell when you want it. Like this:

{
    echo 'x() { echo "I am function x and my argv0 is ${0}." ; }' >./fn_x                                                                                                                                                         
    echo '. ./fn_x ; x' >./script                                                                                                                                                                                                 
    chmod +x ./script && ./script                                                                                                                                                                                                 
    . ./fn_x ; x                                                                                                                                                                                                                  
}
###OUTPUT###
I am function x and my argv0 is ./script.
I am function x and my argv0 is sh.

To have this always available in an interactive shell add a . /path/to/fn_x to your shell's ENV file. For example, for bash with a script containing functions called foo located in /usr/bin you could add this line to ~/.bashrc:

. /usr/bin/foo

If the script is for whatever reason not available at that location during startup your shell will still read in and source the rest of the ENV file as expected, though there will be a diagnostic message printed to stderr to let you know there was an issue sourcing your function definition file.

1
  • Heh, I suppose the short answer is that "sorry, unless you source it you can't use the functions". For interactive shells you can write one source line in ~/.bashrc or such, but for non-interactive shells running scripts there's no other go other than to source it per script. This is pretty much like having to do #include or import in other languages: (almost) no function definitions are implicit...
    – jamadagni
    Oct 2, 2015 at 7:23
5

mikeserv's answer is good for the details of what goes on "behind the scenes", but I feel another answer here is warranted as it doesn't contain a simple usable answer to the exact title question:

How can I get this script file's functions to load without having to source it every time?

The answer is: Source it from your .bashrc or your .bash_profile so it is available in each shell you run.

For example, I have the following in my .bash_profile:

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.